diff --git a/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py b/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py index baa39bf1a..cd7483e51 100644 --- a/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +++ b/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py @@ -118,9 +118,6 @@ class BulkToolCaller(MCPMixin): async with Client(self.connection) as client: result = await client.call_tool_mcp(name=tool, arguments=arguments) - return CallToolRequestResult( - tool=tool, - arguments=arguments, - isError=result.isError, - content=result.content, + return CallToolRequestResult.from_call_tool_result( + result, tool=tool, arguments=arguments ) diff --git a/src/fastmcp/server/middleware/__init__.py b/src/fastmcp/server/middleware/__init__.py index 52678c024..e68c663cf 100644 --- a/src/fastmcp/server/middleware/__init__.py +++ b/src/fastmcp/server/middleware/__init__.py @@ -1,9 +1,14 @@ +from typing import TYPE_CHECKING + from .middleware import ( Middleware, MiddlewareContext, CallNext, ) +if TYPE_CHECKING: + from .bulk_tool_caller import BulkToolCallerMiddleware + def __getattr__(name: str): if name == "BulkToolCallerMiddleware": @@ -13,6 +18,11 @@ def __getattr__(name: str): raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +def __dir__() -> list[str]: + """Ensure BulkToolCallerMiddleware shows up in dir() output.""" + return sorted([*globals().keys(), "BulkToolCallerMiddleware"]) + + __all__ = [ "BulkToolCallerMiddleware", "CallNext", diff --git a/src/fastmcp/server/middleware/bulk_tool_caller.py b/src/fastmcp/server/middleware/bulk_tool_caller.py index 279654065..c6c1ed5c6 100644 --- a/src/fastmcp/server/middleware/bulk_tool_caller.py +++ b/src/fastmcp/server/middleware/bulk_tool_caller.py @@ -2,7 +2,7 @@ from typing import Annotated -from mcp.types import CallToolResult, TextContent +from mcp.types import TextContent from fastmcp.server.context import Context from fastmcp.server.middleware.bulk_tool_caller_types import ( @@ -46,16 +46,15 @@ async def call_tools_bulk( key=tool_call.tool, arguments=tool_call.arguments ) - # Convert ToolResult to CallToolResult - # Don't set isError - it defaults to None for successful calls - call_tool_result = CallToolResult( - content=tool_result.content, - ) - - # Convert to CallToolRequestResult + # Convert ToolResult to CallToolRequestResult, preserving all fields + # Note: ToolResult doesn't have isError - it's only on CallToolResult + # For successful calls, we don't set isError (defaults to None) results.append( - CallToolRequestResult.from_call_tool_result( - call_tool_result, tool_call.tool, tool_call.arguments + CallToolRequestResult( + tool=tool_call.tool, + arguments=tool_call.arguments, + content=tool_result.content, + structuredContent=tool_result.structured_content, ) ) except Exception as e: @@ -111,16 +110,15 @@ async def call_tool_bulk( key=tool, arguments=args ) - # Convert ToolResult to CallToolResult - # Don't set isError - it defaults to None for successful calls - call_tool_result = CallToolResult( - content=tool_result.content, - ) - - # Convert to CallToolRequestResult + # Convert ToolResult to CallToolRequestResult, preserving all fields + # Note: ToolResult doesn't have isError - it's only on CallToolResult + # For successful calls, we don't set isError (defaults to None) results.append( - CallToolRequestResult.from_call_tool_result( - call_tool_result, tool, args + CallToolRequestResult( + tool=tool, + arguments=args, + content=tool_result.content, + structuredContent=tool_result.structured_content, ) ) except Exception as e: diff --git a/src/fastmcp/server/middleware/bulk_tool_caller_types.py b/src/fastmcp/server/middleware/bulk_tool_caller_types.py index b5efbc055..b9a805cc9 100644 --- a/src/fastmcp/server/middleware/bulk_tool_caller_types.py +++ b/src/fastmcp/server/middleware/bulk_tool_caller_types.py @@ -36,4 +36,6 @@ class CallToolRequestResult(CallToolResult): arguments=arguments, isError=result.isError, content=result.content, + _meta=getattr(result, "_meta", None), + structuredContent=getattr(result, "structuredContent", None), ) diff --git a/tests/server/middleware/test_bulk_tool_caller.py b/tests/server/middleware/test_bulk_tool_caller.py index 4d859ff9d..05b8c7561 100644 --- a/tests/server/middleware/test_bulk_tool_caller.py +++ b/tests/server/middleware/test_bulk_tool_caller.py @@ -29,7 +29,11 @@ def server_with_tools(): @mcp.tool async def no_return_tool(arg1: str) -> None: - """A simple tool that returns nothing.""" + """A simple tool that returns nothing. + + Returns: + None: This tool does not return any value. + """ @mcp.tool def add(a: int, b: int) -> int: @@ -78,7 +82,7 @@ class TestBulkToolCallerMiddleware: "_meta": None, } ], - "structuredContent": None, + "structuredContent": {"result": "value1"}, "isError": False, "tool": "echo_tool", "arguments": {"arg1": "value1"}, @@ -110,7 +114,7 @@ class TestBulkToolCallerMiddleware: "_meta": None, } ], - "structuredContent": None, + "structuredContent": {"result": "value1"}, "isError": False, "tool": "echo_tool", "arguments": {"arg1": "value1"}, @@ -125,7 +129,7 @@ class TestBulkToolCallerMiddleware: "_meta": None, } ], - "structuredContent": None, + "structuredContent": {"result": "value2"}, "isError": False, "tool": "echo_tool", "arguments": {"arg1": "value2"}, @@ -197,7 +201,7 @@ class TestBulkToolCallerMiddleware: "_meta": None, } ], - "structuredContent": None, + "structuredContent": {"result": "value1"}, "isError": False, "tool": "echo_tool", "arguments": {"arg1": "value1"},