Skip to content

Consolidate execution method chains into single public API#2728

Merged
jlowin merged 4 commits intomainfrom
consolidate-execution-methods
Dec 25, 2025
Merged

Consolidate execution method chains into single public API#2728
jlowin merged 4 commits intomainfrom
consolidate-execution-methods

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 25, 2025

Following the pattern from #2719, this consolidates the execution method chains into single public methods with a run_middleware parameter.

New Public API

# Execute a tool
result = await server.call_tool("add", {"a": 1, "b": 2})

# Read a resource
contents = await server.read_resource("resource://test")

# Render a prompt
prompt = await server.render_prompt("greet", {"name": "World"})

# Skip middleware when calling programmatically
result = await server.call_tool("add", {"a": 1, "b": 2}, run_middleware=False)

Changes

Each method consolidates the previous 3-4 method chain (e.g., _call_tool_middleware_call_tool_execute_tool) into a single method using lambda recursion for middleware application.

Also renames apply_middlewarerun_middleware across the codebase for clarity.

Deleted private methods:

  • _call_tool_middleware, _call_tool, _execute_tool
  • _read_resource_middleware, _read_resource, _execute_resource, _execute_template
  • _get_prompt_content_middleware, _get_prompt, _execute_prompt

Not a breaking change since all removed methods were private (_*).

Add call_tool(), read_resource(), render_prompt() methods with apply_middleware parameter, following the pattern from #2719.
@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

Warning

Rate limit exceeded

@jlowin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b4a2753 and 20c8931.

⛔ Files ignored due to path filters (3)
  • tests/contrib/test_component_manager.py is excluded by none and included by none
  • tests/server/providers/test_local_provider_tools.py is excluded by none and included by none
  • tests/server/test_server.py is excluded by none and included by none
📒 Files selected for processing (2)
  • src/fastmcp/contrib/component_manager/component_manager.py
  • src/fastmcp/contrib/component_manager/component_service.py

Walkthrough

Centralizes tool invocation, resource reading, and prompt rendering behind three new FastMCP public methods: call_tool, read_resource, and render_prompt, and renames the internal middleware entrypoint from _apply_middleware to _run_middleware. Provider wrapper classes (Tool, Resource, Prompt, ResourceTemplate, TransformingProvider) now delegate directly to those new server methods and simplify internal logic by removing prior middleware-specific context management. Listing/getter APIs and internal MCP handlers were updated to use the new run_middleware flag.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: consolidating execution method chains into a single public API.
Description check ✅ Passed The description is comprehensive, covering new public APIs, key changes, deleted methods, and clarifying this is not a breaking change. However, the Contributors Checklist is completely unchecked.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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/server/server.py (1)

1552-1555: Minor inconsistency in error message formatting.

The call_tool() method raises NotFoundError(f"Unknown tool: {name!r}") with !r formatting, but this handler re-raises with NotFoundError(f"Unknown tool: {key}") without it. Consider using consistent formatting:

-        except NotFoundError as e:
-            raise NotFoundError(f"Unknown tool: {key}") from e
+        except NotFoundError as e:
+            raise NotFoundError(f"Unknown tool: {key!r}") from e

Also applies to the DisabledError handling on line 1553.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d17334 and 1977e11.

⛔ Files ignored due to path filters (1)
  • tests/server/middleware/test_middleware.py is excluded by none and included by none
📒 Files selected for processing (2)
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/server/server.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use Python ≥ 3.10 with full type annotations
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/server/server.py
🧠 Learnings (3)
📓 Common learnings
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/providers/fastmcp_provider.py
  • src/fastmcp/server/server.py
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/**/__init__.py : Core types that define a module's purpose should be exported (e.g., `Middleware` from `fastmcp.server.middleware`), while specialized features can live in submodules

Applied to files:

  • src/fastmcp/server/server.py
🧬 Code graph analysis (2)
src/fastmcp/server/providers/fastmcp_provider.py (3)
src/fastmcp/server/server.py (3)
  • call_tool (1145-1209)
  • read_resource (1211-1305)
  • render_prompt (1307-1370)
src/fastmcp/resources/resource.py (1)
  • Resource (137-315)
src/fastmcp/prompts/prompt.py (1)
  • Prompt (118-306)
src/fastmcp/server/server.py (9)
src/fastmcp/tools/tool.py (3)
  • ToolResult (78-124)
  • Tool (127-352)
  • to_mcp_result (111-124)
src/fastmcp/server/context.py (4)
  • fastmcp (169-174)
  • Context (115-1061)
  • read_resource (308-318)
  • get_prompt (294-306)
src/fastmcp/server/providers/fastmcp_provider.py (6)
  • get_tool (432-435)
  • get_resource (451-454)
  • _read (143-149)
  • _read (280-317)
  • get_resource_template (471-477)
  • get_prompt (492-495)
src/fastmcp/server/providers/base.py (4)
  • get_tool (142-152)
  • get_resource (161-171)
  • get_resource_template (180-194)
  • get_prompt (203-213)
src/fastmcp/server/providers/transforming.py (4)
  • get_tool (177-185)
  • get_resource (199-207)
  • get_resource_template (223-235)
  • get_prompt (249-257)
src/fastmcp/server/providers/openapi/provider.py (3)
  • get_tool (355-357)
  • get_resource (363-365)
  • get_resource_template (371-376)
src/fastmcp/server/middleware/tool_injection.py (2)
  • read_resource (98-103)
  • get_prompt (64-72)
src/fastmcp/resources/resource.py (4)
  • ResourceContent (39-134)
  • _read (225-259)
  • key (286-288)
  • Resource (137-315)
src/fastmcp/resources/template.py (4)
  • _read (171-203)
  • _read (290-313)
  • matches (154-156)
  • key (251-253)
🪛 Ruff (0.14.10)
src/fastmcp/server/server.py

1206-1206: Avoid specifying long messages outside the exception class

(TRY003)


1207-1207: Avoid specifying long messages outside the exception class

(TRY003)


1209-1209: Avoid specifying long messages outside the exception class

(TRY003)


1267-1267: Consider moving this statement to an else block

(TRY300)


1274-1276: Avoid specifying long messages outside the exception class

(TRY003)


1277-1279: Avoid specifying long messages outside the exception class

(TRY003)


1291-1291: Consider moving this statement to an else block

(TRY300)


1298-1300: Avoid specifying long messages outside the exception class

(TRY003)


1301-1303: Avoid specifying long messages outside the exception class

(TRY003)


1305-1305: Avoid specifying long messages outside the exception class

(TRY003)


1365-1365: Avoid specifying long messages outside the exception class

(TRY003)


1366-1368: Avoid specifying long messages outside the exception class

(TRY003)


1370-1370: Avoid specifying long messages outside the exception class

(TRY003)


1553-1553: Avoid specifying long messages outside the exception class

(TRY003)


1555-1555: Avoid specifying long messages outside the exception class

(TRY003)


1599-1599: Avoid specifying long messages outside the exception class

(TRY003)


1601-1601: Avoid specifying long messages outside the exception class

(TRY003)


1643-1643: Avoid specifying long messages outside the exception class

(TRY003)


1645-1645: Avoid specifying long messages outside the exception class

(TRY003)


1660-1660: Prefer TypeError exception for invalid type

(TRY004)


1660-1660: Avoid specifying long messages outside the exception class

(TRY003)


1661-1661: Consider moving this statement to an else block

(TRY300)


1664-1664: Avoid specifying long messages outside the exception class

(TRY003)


1667-1667: Avoid specifying long messages outside the exception class

(TRY003)


1685-1685: Prefer TypeError exception for invalid type

(TRY004)


1685-1685: Avoid specifying long messages outside the exception class

(TRY003)


1689-1689: Avoid specifying long messages outside the exception class

(TRY003)


1692-1692: 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.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 (11)
src/fastmcp/server/providers/fastmcp_provider.py (4)

100-105: LGTM!

The delegation to call_tool() is correct and the return type matches the server method's signature.


151-163: LGTM!

The delegation and result unwrapping are correct. The result[0] access is safe because read_resource() guarantees a non-empty list when returning list[ResourceContent].


209-220: LGTM!

The delegation to render_prompt() is correct with proper type alignment.


280-317: LGTM!

The delegation with proper contextvar management and result unwrapping is correct. The finally block ensures proper token reset.

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

1145-1209: LGTM!

The new public API is well-structured with proper type annotations, comprehensive exception handling, and correct middleware application pattern.


1211-1305: LGTM!

The two-pass approach (concrete resources first, then templates) is correct and consistent with existing lookup patterns. The defensive template.matches(uri) check provides extra safety.


1307-1370: LGTM!

The prompt rendering follows the established pattern with proper exception handling and type safety.


1579-1601: LGTM with same minor note.

Contextvar handling and result conversion are correct. The same !r formatting inconsistency exists here as in _call_tool_mcp (line 1599 vs read_resource line 1305).


1626-1645: LGTM!

The handler follows the established pattern correctly with proper contextvar management and exception handling.


1647-1667: LGTM!

The defensive check for unexpected CreateTaskResult with RuntimeError is appropriate - this represents a logic invariant violation ("should never happen without task metadata") rather than a type error. The static analysis suggestion for TypeError can be safely ignored here.


1669-1692: LGTM!

The implementation correctly follows the established pattern with proper result conversion and defensive error handling.

Also fix inspect.py to skip middleware during introspection.
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/server/server.py (1)

1143-1207: Context is created unconditionally but only used when run_middleware=True.

The Context is entered at line 1169 regardless of the run_middleware flag, but it's only used within the if run_middleware: block (lines 1170-1187). When run_middleware=False, the context is entered and exited without being used, adding unnecessary overhead.

Consider restructuring to only create the context when needed:

🔎 Suggested refactor
     async def call_tool(
         self,
         name: str,
         arguments: dict[str, Any] | None = None,
         *,
         run_middleware: bool = True,
     ) -> ToolResult | mcp.types.CreateTaskResult:
         ...
-        async with fastmcp.server.context.Context(fastmcp=self) as ctx:
-            if run_middleware:
-                mw_context = MiddlewareContext[CallToolRequestParams](
-                    message=mcp.types.CallToolRequestParams(
-                        name=name, arguments=arguments or {}
-                    ),
-                    source="client",
-                    type="request",
-                    method="tools/call",
-                    fastmcp_context=ctx,
-                )
-                return await self._run_middleware(
-                    context=mw_context,
-                    call_next=lambda context: self.call_tool(
-                        context.message.name,
-                        context.message.arguments or {},
-                        run_middleware=False,
-                    ),
-                )
-
-            # Core logic: find and execute tool
+        if run_middleware:
+            async with fastmcp.server.context.Context(fastmcp=self) as ctx:
+                mw_context = MiddlewareContext[CallToolRequestParams](
+                    message=mcp.types.CallToolRequestParams(
+                        name=name, arguments=arguments or {}
+                    ),
+                    source="client",
+                    type="request",
+                    method="tools/call",
+                    fastmcp_context=ctx,
+                )
+                return await self._run_middleware(
+                    context=mw_context,
+                    call_next=lambda context: self.call_tool(
+                        context.message.name,
+                        context.message.arguments or {},
+                        run_middleware=False,
+                    ),
+                )
+
+        # Core logic: find and execute tool
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1977e11 and d30a4c5.

⛔ Files ignored due to path filters (2)
  • tests/server/middleware/test_middleware.py is excluded by none and included by none
  • tests/server/test_tool_transformation.py is excluded by none and included by none
📒 Files selected for processing (4)
  • src/fastmcp/server/low_level.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/server/server.py
  • src/fastmcp/utilities/inspect.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use Python ≥ 3.10 with full type annotations
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/server/low_level.py
  • src/fastmcp/utilities/inspect.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/providers/fastmcp_provider.py
🧠 Learnings (4)
📓 Common learnings
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
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: When modifying MCP functionality (Tools, Resources, Resource Templates, Prompts), changes typically need to be applied across all object types
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/**/__init__.py : Core types that define a module's purpose should be exported (e.g., `Middleware` from `fastmcp.server.middleware`), while specialized features can live in submodules
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/**/__init__.py : Core types that define a module's purpose should be exported (e.g., `Middleware` from `fastmcp.server.middleware`), while specialized features can live in submodules

Applied to files:

  • src/fastmcp/server/low_level.py
  • src/fastmcp/server/server.py
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/__init__.py : All module exports should be intentional - only re-export to `fastmcp.*` for fundamental types like `FastMCP` and `Client`, prefer users importing from specific submodules for specialized features

Applied to files:

  • src/fastmcp/server/server.py
📚 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/providers/fastmcp_provider.py
🧬 Code graph analysis (4)
src/fastmcp/server/low_level.py (2)
src/fastmcp/server/context.py (1)
  • fastmcp (169-174)
src/fastmcp/server/server.py (1)
  • _run_middleware (695-704)
src/fastmcp/utilities/inspect.py (1)
src/fastmcp/server/server.py (4)
  • get_tools (793-835)
  • get_prompts (1040-1084)
  • get_resources (890-937)
  • get_resource_templates (962-1013)
src/fastmcp/server/server.py (4)
src/fastmcp/resources/resource.py (2)
  • Resource (137-315)
  • key (286-288)
src/fastmcp/prompts/prompt.py (3)
  • Prompt (118-306)
  • PromptResult (73-115)
  • to_mcp_prompt_result (109-115)
src/fastmcp/server/middleware/middleware.py (1)
  • MiddlewareContext (47-63)
src/fastmcp/server/providers/base.py (4)
  • get_tool (142-152)
  • get_resource (161-171)
  • get_resource_template (180-194)
  • get_prompt (203-213)
src/fastmcp/server/providers/fastmcp_provider.py (3)
src/fastmcp/server/server.py (6)
  • read_resource (1209-1303)
  • render_prompt (1305-1368)
  • get_tools (793-835)
  • get_resources (890-937)
  • get_resource_templates (962-1013)
  • get_prompts (1040-1084)
src/fastmcp/resources/resource.py (1)
  • Resource (137-315)
src/fastmcp/prompts/prompt.py (1)
  • Prompt (118-306)
🪛 Ruff (0.14.10)
src/fastmcp/server/server.py

815-815: Unused lambda argument: context

(ARG005)


912-912: Unused lambda argument: context

(ARG005)


986-986: Unused lambda argument: context

(ARG005)


1062-1062: Unused lambda argument: context

(ARG005)


1204-1204: Avoid specifying long messages outside the exception class

(TRY003)


1205-1205: Avoid specifying long messages outside the exception class

(TRY003)


1207-1207: Avoid specifying long messages outside the exception class

(TRY003)


1265-1265: Consider moving this statement to an else block

(TRY300)


1272-1274: Avoid specifying long messages outside the exception class

(TRY003)


1275-1277: Avoid specifying long messages outside the exception class

(TRY003)


1289-1289: Consider moving this statement to an else block

(TRY300)


1296-1298: Avoid specifying long messages outside the exception class

(TRY003)


1299-1301: Avoid specifying long messages outside the exception class

(TRY003)


1303-1303: Avoid specifying long messages outside the exception class

(TRY003)


1363-1363: Avoid specifying long messages outside the exception class

(TRY003)


1364-1366: Avoid specifying long messages outside the exception class

(TRY003)


1368-1368: Avoid specifying long messages outside the exception class

(TRY003)


1551-1551: Avoid specifying long messages outside the exception class

(TRY003)


1553-1553: Avoid specifying long messages outside the exception class

(TRY003)


1597-1597: Avoid specifying long messages outside the exception class

(TRY003)


1599-1599: Avoid specifying long messages outside the exception class

(TRY003)


1641-1641: Avoid specifying long messages outside the exception class

(TRY003)


1643-1643: Avoid specifying long messages outside the exception class

(TRY003)


1658-1658: Prefer TypeError exception for invalid type

(TRY004)


1658-1658: Avoid specifying long messages outside the exception class

(TRY003)


1659-1659: Consider moving this statement to an else block

(TRY300)


1662-1662: Avoid specifying long messages outside the exception class

(TRY003)


1665-1665: Avoid specifying long messages outside the exception class

(TRY003)


1683-1683: Prefer TypeError exception for invalid type

(TRY004)


1683-1683: Avoid specifying long messages outside the exception class

(TRY003)


1687-1687: Avoid specifying long messages outside the exception class

(TRY003)


1690-1690: 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.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
🔇 Additional comments (16)
src/fastmcp/server/low_level.py (1)

109-111: LGTM!

The method rename from _apply_middleware to _run_middleware is consistent with the broader refactoring in server.py. The call signature and behavior remain unchanged.

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

695-704: LGTM!

The _run_middleware method correctly builds and executes the middleware chain. The reverse iteration ensures middleware runs in registration order (first registered = outermost wrapper).


793-817: LGTM!

The run_middleware parameter provides a clean API for middleware-aware vs. direct access. The lambda ignoring the context parameter is intentional—the context is only needed for middleware processing, not the actual data fetch.


1209-1303: LGTM!

The read_resource method correctly implements the two-pass lookup (concrete resources first, then templates) with proper error handling and masking. The same Context optimization noted for call_tool applies here as well.


1305-1368: LGTM!

The render_prompt method follows the same consistent pattern as call_tool and read_resource, with proper provider iteration and error handling.


1499-1553: LGTM!

The _call_tool_mcp handler correctly manages task metadata contextvars with proper token-based cleanup in the finally block. The exception handling appropriately standardizes error messages.


1555-1599: LGTM!

The _read_resource_handler correctly implements custom handling for CreateTaskResult support, with proper contextvar management and MCP result wrapping.


1601-1643: LGTM!

The _get_prompt_handler follows the same consistent pattern as _read_resource_handler, with proper contextvar management and MCP result handling.


1645-1690: LGTM!

The helper methods correctly delegate to the public APIs and include appropriate safety checks for unexpected CreateTaskResult returns. The RuntimeError is appropriate for internal invariant violations.


1431-1497: LGTM!

The MCP listing handlers correctly invoke get_* methods with run_middleware=True, ensuring middleware is applied for client-facing protocol handlers.

src/fastmcp/server/providers/fastmcp_provider.py (5)

97-105: LGTM!

The delegation to call_tool() is correct and maintains the proper mapping via _original_name.


151-163: LGTM!

The delegation correctly handles both CreateTaskResult pass-through and normal results. Extracting result[0] is appropriate since read_resource() returns a list.


209-220: LGTM!

The delegation to render_prompt() is straightforward and correct.


280-317: LGTM!

The template's _read() correctly manages the _docket_fn_key contextvar and delegates to read_resource() with proper result unwrapping.


422-495: LGTM!

The provider's list methods correctly invoke the wrapped server's get_* methods with run_middleware=True, ensuring middleware is applied for mounted server discovery.

src/fastmcp/utilities/inspect.py (1)

109-113: LGTM!

Bypassing middleware for inspection is the correct behavior. The comment clearly explains the rationale: auth and rate-limiting middleware should not affect introspection of available components.

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/server/server.py (1)

1259-1265: Optional: Consider restructuring CreateTaskResult handling.

The mime_type assignment at lines 1263-1265 could be moved into an else block after the CreateTaskResult check for clearer flow:

                     try:
                         result = await resource._read()
                         if isinstance(result, mcp.types.CreateTaskResult):
                             return result
-                        if result.mime_type is None:
-                            result.mime_type = resource.mime_type
-                        return [result]
+                        else:
+                            if result.mime_type is None:
+                                result.mime_type = resource.mime_type
+                            return [result]

This makes it explicit that the mime_type logic only applies to non-task results.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d30a4c5 and b4a2753.

📒 Files selected for processing (1)
  • src/fastmcp/server/server.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use Python ≥ 3.10 with full type annotations
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/server/server.py
🧠 Learnings (3)
📓 Common learnings
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
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: When modifying MCP functionality (Tools, Resources, Resource Templates, Prompts), changes typically need to be applied across all object types
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/**/__init__.py : Core types that define a module's purpose should be exported (e.g., `Middleware` from `fastmcp.server.middleware`), while specialized features can live in submodules
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/**/__init__.py : Core types that define a module's purpose should be exported (e.g., `Middleware` from `fastmcp.server.middleware`), while specialized features can live in submodules

Applied to files:

  • src/fastmcp/server/server.py
📚 Learning: 2025-12-21T21:37:55.031Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T21:37:55.031Z
Learning: Applies to src/fastmcp/__init__.py : All module exports should be intentional - only re-export to `fastmcp.*` for fundamental types like `FastMCP` and `Client`, prefer users importing from specific submodules for specialized features

Applied to files:

  • src/fastmcp/server/server.py
🧬 Code graph analysis (1)
src/fastmcp/server/server.py (5)
src/fastmcp/tools/tool.py (4)
  • Tool (127-352)
  • ToolResult (78-124)
  • _run (268-291)
  • to_mcp_result (111-124)
src/fastmcp/prompts/prompt.py (4)
  • Prompt (118-306)
  • PromptResult (73-115)
  • _render (238-277)
  • to_mcp_prompt_result (109-115)
src/fastmcp/server/context.py (4)
  • fastmcp (169-174)
  • Context (115-1061)
  • read_resource (308-318)
  • get_prompt (294-306)
src/fastmcp/server/middleware/middleware.py (1)
  • MiddlewareContext (47-63)
src/fastmcp/server/providers/base.py (4)
  • get_tool (142-152)
  • get_resource (161-171)
  • get_resource_template (180-194)
  • get_prompt (203-213)
🪛 Ruff (0.14.10)
src/fastmcp/server/server.py

815-815: Unused lambda argument: context

(ARG005)


858-858: Avoid specifying long messages outside the exception class

(TRY003)


912-912: Unused lambda argument: context

(ARG005)


986-986: Unused lambda argument: context

(ARG005)


1062-1062: Unused lambda argument: context

(ARG005)


1204-1204: Avoid specifying long messages outside the exception class

(TRY003)


1205-1205: Avoid specifying long messages outside the exception class

(TRY003)


1207-1207: Avoid specifying long messages outside the exception class

(TRY003)


1265-1265: Consider moving this statement to an else block

(TRY300)


1272-1274: Avoid specifying long messages outside the exception class

(TRY003)


1275-1277: Avoid specifying long messages outside the exception class

(TRY003)


1289-1289: Consider moving this statement to an else block

(TRY300)


1296-1298: Avoid specifying long messages outside the exception class

(TRY003)


1299-1301: Avoid specifying long messages outside the exception class

(TRY003)


1303-1303: Avoid specifying long messages outside the exception class

(TRY003)


1363-1363: Avoid specifying long messages outside the exception class

(TRY003)


1364-1366: Avoid specifying long messages outside the exception class

(TRY003)


1368-1368: Avoid specifying long messages outside the exception class

(TRY003)


1551-1551: Avoid specifying long messages outside the exception class

(TRY003)


1553-1553: Avoid specifying long messages outside the exception class

(TRY003)


1597-1597: Avoid specifying long messages outside the exception class

(TRY003)


1599-1599: Avoid specifying long messages outside the exception class

(TRY003)


1641-1641: Avoid specifying long messages outside the exception class

(TRY003)


1643-1643: Avoid specifying long messages outside the exception class

(TRY003)


1658-1658: Prefer TypeError exception for invalid type

(TRY004)


1658-1658: Avoid specifying long messages outside the exception class

(TRY003)


1659-1659: Consider moving this statement to an else block

(TRY300)


1662-1662: Avoid specifying long messages outside the exception class

(TRY003)


1665-1665: Avoid specifying long messages outside the exception class

(TRY003)


1683-1683: Prefer TypeError exception for invalid type

(TRY004)


1683-1683: Avoid specifying long messages outside the exception class

(TRY003)


1687-1687: Avoid specifying long messages outside the exception class

(TRY003)


1690-1690: 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). (3)
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (8)
src/fastmcp/server/server.py (8)

793-817: Lambda argument unused by design in middleware recursion.

The context parameter in the lambda at line 815 is unused because the recursive call bypasses middleware. This matches the pattern in the new public APIs (call_tool, read_resource, render_prompt) where the lambda extracts arguments from context.message before recursing. However, for list operations there are no arguments to extract from the context, so the simpler pattern is appropriate.

The static analysis warning (ARG005) can be safely ignored here.


1143-1207: LGTM: Well-structured public API for tool execution.

The implementation correctly:

  • Applies middleware by default while allowing bypass via run_middleware=False
  • Extracts arguments from middleware context before recursive calls
  • Handles task routing via tool._run()
  • Provides comprehensive error handling with optional masking

The static analysis warnings about exception messages (TRY003) can be ignored—inline messages are appropriate for user-facing errors.


1305-1368: LGTM: Consistent implementation for prompt rendering.

The render_prompt method follows the same well-structured pattern as call_tool, with proper middleware support, task routing, and error handling.


1439-1496: LGTM: MCP list handlers properly invoke middleware.

The updates ensure middleware chains execute for all MCP list operations, maintaining consistency with the new public API pattern.


1526-1553: LGTM: Proper task metadata and contextvar management.

The handler correctly:

  • Extracts SEP-1686 task metadata from the SDK request context
  • Sets contextvars for tool._run() to access
  • Resets contextvars in a finally block for cleanup
  • Converts DisabledError to NotFoundError to avoid leaking component presence

The static analysis warnings about exception messages can be ignored.


1577-1599: LGTM: Consistent task-aware resource handler.

The implementation follows the same correct pattern as _call_tool_mcp with proper contextvar management and error handling.


1624-1643: LGTM: Consistent task-aware prompt handler.

The implementation maintains the same correct pattern with proper contextvar lifecycle management and security-conscious error handling.


1656-1665: Defensive guards appropriate for internal consistency.

The RuntimeError checks at lines 1658 and 1683 are correct defensive programming to catch unexpected CreateTaskResult returns when no task metadata is present. The static analysis suggestion (TRY004) to use TypeError is incorrect—RuntimeError is appropriate for internal invariant violations rather than type validation.

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: Two tests are failing because error messages now include quotes around tool names (e.g., 'add' instead of add), but test regex patterns expect the old format without quotes.

Root Cause: PR #2728 changed error messages in src/fastmcp/server/server.py to use !r formatting (repr), which adds quotes around tool names. For example:

  • Line 860: raise NotFoundError(f"Unknown tool: {name}")raise NotFoundError(f"Unknown tool: {name!r}")
  • Lines 1329-1331: Similar changes with {key!r}

The failing tests use regex patterns that expect the old format:

  • tests/server/providers/test_local_provider_tools.py:1168: match="Unknown tool: add"
  • tests/server/test_server.py:94: match="Unknown tool: adder"

Suggested Solution: Update the regex patterns in the two failing tests to match the new quoted format:

  1. tests/server/providers/test_local_provider_tools.py:1168

    # Change from:
    with pytest.raises(NotFoundError, match="Unknown tool: add"):
    # To:
    with pytest.raises(NotFoundError, match="Unknown tool: 'add'"):
    # Or use a regex pattern:
    with pytest.raises(NotFoundError, match=r"Unknown tool: ['\"]add['\"]"):
  2. tests/server/test_server.py:94

    # Change from:
    with pytest.raises(NotFoundError, match="Unknown tool: adder"):
    # To:
    with pytest.raises(NotFoundError, match="Unknown tool: 'adder'"):
    # Or use a regex pattern:
    with pytest.raises(NotFoundError, match=r"Unknown tool: ['\"]adder['\"]"):
Detailed Analysis

Failed Test Output

FAILED tests/server/providers/test_local_provider_tools.py::TestToolDecorator::test_no_tools_before_decorator - AssertionError: Regex pattern did not match.
  Expected regex: 'Unknown tool: add'
  Actual message: "Unknown tool: 'add'"

FAILED tests/server/test_server.py::TestTools::test_remove_tool_successfully - AssertionError: Regex pattern did not match.
  Expected regex: 'Unknown tool: adder'
  Actual message: "Unknown tool: 'adder'"

Related Changes in PR

The PR consolidated execution methods and changed error message formatting to use !r (repr) for consistency. This is evident in the diff:

# Before
raise NotFoundError(f"Unknown tool: {name}")

# After
raise NotFoundError(f"Unknown tool: {name!r}")

The !r format specifier calls repr() on the value, which for strings adds quotes.

Impact

  • Only affects test assertions that check error message text
  • No functional impact on the actual error handling
  • The change is actually an improvement as it makes error messages clearer (quoted strings are more readable in error messages)
Related Files
  • src/fastmcp/server/server.py:860 - Error message with {name!r}
  • src/fastmcp/server/server.py:1329-1331 - Error messages with {key!r}
  • tests/server/providers/test_local_provider_tools.py:1168 - Test expecting old format
  • tests/server/test_server.py:94 - Test expecting old format

@jlowin jlowin merged commit b05801d into main Dec 25, 2025
11 checks passed
@jlowin jlowin deleted the consolidate-execution-methods branch December 25, 2025 15:38
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