diff --git a/src/fastmcp/client/client.py b/src/fastmcp/client/client.py index e5f27aa6b..7ca6528d1 100644 --- a/src/fastmcp/client/client.py +++ b/src/fastmcp/client/client.py @@ -400,12 +400,9 @@ class Client: self, name: str, arguments: dict[str, Any] | None = None, - ) -> ( - list[ - mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource - ] - | mcp.types.CallToolResult - ): + ) -> list[ + mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource + ]: """Call a tool on the server. Unlike call_tool_mcp, this method raises a ClientError if the tool call results in an error. 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 365dd95b7..07e82e8aa 100644 --- a/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +++ b/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py @@ -123,9 +123,7 @@ class BulkToolCaller(MCPMixin): """ async with Client(self.connection) as client: - result = await client.call_tool( - name=tool, arguments=arguments, _return_raw_result=True - ) + result = await client.call_tool_mcp(name=tool, arguments=arguments) return CallToolRequestResult( tool=tool, diff --git a/src/fastmcp/server/proxy.py b/src/fastmcp/server/proxy.py index ca16e5246..ae0370a82 100644 --- a/src/fastmcp/server/proxy.py +++ b/src/fastmcp/server/proxy.py @@ -65,8 +65,9 @@ class ProxyTool(Tool): # the client context manager will swallow any exceptions inside a TaskGroup # so we return the raw result and raise an exception ourselves async with self._client: - result = await self._client.call_tool( - self.name, arguments, _return_raw_result=True + result = await self._client.call_tool_mcp( + name=self.name, + arguments=arguments, ) if result.isError: raise ValueError(cast(mcp.types.TextContent, result.content[0]).text) diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 7bc0fe22e..c6d671fc4 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -79,6 +79,19 @@ async def test_list_tools(fastmcp_server): assert set(tool.name for tool in result) == {"greet", "add"} +async def test_list_tools_mcp(fastmcp_server): + """Test the list_tools_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + result = await client.list_tools_mcp() + + # Check that we got the raw MCP ListToolsResult object + assert hasattr(result, "tools") + assert len(result.tools) == 2 + assert set(tool.name for tool in result.tools) == {"greet", "add"} + + async def test_call_tool(fastmcp_server): """Test calling a tool with InMemoryClient.""" client = Client(transport=FastMCPTransport(fastmcp_server)) @@ -91,6 +104,26 @@ async def test_call_tool(fastmcp_server): assert "Hello, World!" in content_str +async def test_call_tool_mcp(fastmcp_server): + """Test the call_tool_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + result = await client.call_tool_mcp("greet", {"name": "World"}) + + # Check that we got the raw MCP CallToolResult object + assert hasattr(result, "content") + assert hasattr(result, "isError") + assert result.isError is False + # The content is a list, so we'll check the first element + # by properly accessing it + content = result.content + assert len(content) > 0 + first_content = content[0] + content_str = str(first_content) + assert "Hello, World!" in content_str + + async def test_list_resources(fastmcp_server): """Test listing resources with InMemoryClient.""" client = Client(transport=FastMCPTransport(fastmcp_server)) @@ -103,6 +136,19 @@ async def test_list_resources(fastmcp_server): assert str(result[0].uri) == "data://users" +async def test_list_resources_mcp(fastmcp_server): + """Test the list_resources_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + result = await client.list_resources_mcp() + + # Check that we got the raw MCP ListResourcesResult object + assert hasattr(result, "resources") + assert len(result.resources) == 1 + assert str(result.resources[0].uri) == "data://users" + + async def test_list_prompts(fastmcp_server): """Test listing prompts with InMemoryClient.""" client = Client(transport=FastMCPTransport(fastmcp_server)) @@ -115,6 +161,19 @@ async def test_list_prompts(fastmcp_server): assert result[0].name == "welcome" +async def test_list_prompts_mcp(fastmcp_server): + """Test the list_prompts_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + result = await client.list_prompts_mcp() + + # Check that we got the raw MCP ListPromptsResult object + assert hasattr(result, "prompts") + assert len(result.prompts) == 1 + assert result.prompts[0].name == "welcome" + + async def test_get_prompt(fastmcp_server): """Test getting a prompt with InMemoryClient.""" client = Client(transport=FastMCPTransport(fastmcp_server)) @@ -127,6 +186,20 @@ async def test_get_prompt(fastmcp_server): assert "Welcome to FastMCP, Developer!" in result_str +async def test_get_prompt_mcp(fastmcp_server): + """Test the get_prompt_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + result = await client.get_prompt_mcp("welcome", {"name": "Developer"}) + + # Check that we got the raw MCP GetPromptResult object + assert hasattr(result, "messages") + assert len(result.messages) > 0 + result_str = str(result.messages) + assert "Welcome to FastMCP, Developer!" in result_str + + async def test_read_resource(fastmcp_server): """Test reading a resource with InMemoryClient.""" client = Client(transport=FastMCPTransport(fastmcp_server)) @@ -145,6 +218,26 @@ async def test_read_resource(fastmcp_server): assert "Charlie" in contents_str +async def test_read_resource_mcp(fastmcp_server): + """Test the read_resource_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + # Use the URI from the resource we know exists in our server + uri = cast( + AnyUrl, "data://users" + ) # Use cast for type hint only, the URI is valid + result = await client.read_resource_mcp(uri) + + # Check that we got the raw MCP ReadResourceResult object + assert hasattr(result, "contents") + assert len(result.contents) > 0 + contents_str = str(result.contents[0]) + assert "Alice" in contents_str + assert "Bob" in contents_str + assert "Charlie" in contents_str + + async def test_client_connection(fastmcp_server): """Test that the client connects and disconnects properly.""" client = Client(transport=FastMCPTransport(fastmcp_server)) @@ -213,6 +306,19 @@ async def test_resource_template(fastmcp_server): assert '"active": true' in content_str +async def test_list_resource_templates_mcp(fastmcp_server): + """Test the list_resource_templates_mcp method that returns raw MCP protocol objects.""" + client = Client(transport=FastMCPTransport(fastmcp_server)) + + async with client: + result = await client.list_resource_templates_mcp() + + # Check that we got the raw MCP ListResourceTemplatesResult object + assert hasattr(result, "resourceTemplates") + assert len(result.resourceTemplates) == 1 + assert "data://user/{user_id}" in result.resourceTemplates[0].uriTemplate + + async def test_mcp_resource_generation(fastmcp_server): """Test that resources are properly generated in MCP format.""" client = Client(transport=FastMCPTransport(fastmcp_server)) diff --git a/tests/server/test_server_interactions.py b/tests/server/test_server_interactions.py index 98025f0bb..9c7ed2b88 100644 --- a/tests/server/test_server_interactions.py +++ b/tests/server/test_server_interactions.py @@ -96,7 +96,7 @@ class TestTools: async def test_call_tool_error_as_client_raw(self, tool_server: FastMCP): async with Client(tool_server) as client: - result = await client.call_tool("error_tool", {}, _return_raw_result=True) + result = await client.call_tool_mcp("error_tool", {}) assert result.isError assert isinstance(result.content[0], TextContent) assert "Test error" in result.content[0].text