Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
7e50621
1
Parent(s): 5d452fd
Update tests
Browse files
src/fastmcp/client/client.py
CHANGED
|
@@ -400,12 +400,9 @@ class Client:
|
|
| 400 |
self,
|
| 401 |
name: str,
|
| 402 |
arguments: dict[str, Any] | None = None,
|
| 403 |
-
) ->
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
]
|
| 407 |
-
| mcp.types.CallToolResult
|
| 408 |
-
):
|
| 409 |
"""Call a tool on the server.
|
| 410 |
|
| 411 |
Unlike call_tool_mcp, this method raises a ClientError if the tool call results in an error.
|
|
|
|
| 400 |
self,
|
| 401 |
name: str,
|
| 402 |
arguments: dict[str, Any] | None = None,
|
| 403 |
+
) -> list[
|
| 404 |
+
mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource
|
| 405 |
+
]:
|
|
|
|
|
|
|
|
|
|
| 406 |
"""Call a tool on the server.
|
| 407 |
|
| 408 |
Unlike call_tool_mcp, this method raises a ClientError if the tool call results in an error.
|
src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py
CHANGED
|
@@ -123,9 +123,7 @@ class BulkToolCaller(MCPMixin):
|
|
| 123 |
"""
|
| 124 |
|
| 125 |
async with Client(self.connection) as client:
|
| 126 |
-
result = await client.
|
| 127 |
-
name=tool, arguments=arguments, _return_raw_result=True
|
| 128 |
-
)
|
| 129 |
|
| 130 |
return CallToolRequestResult(
|
| 131 |
tool=tool,
|
|
|
|
| 123 |
"""
|
| 124 |
|
| 125 |
async with Client(self.connection) as client:
|
| 126 |
+
result = await client.call_tool_mcp(name=tool, arguments=arguments)
|
|
|
|
|
|
|
| 127 |
|
| 128 |
return CallToolRequestResult(
|
| 129 |
tool=tool,
|
src/fastmcp/server/proxy.py
CHANGED
|
@@ -65,8 +65,9 @@ class ProxyTool(Tool):
|
|
| 65 |
# the client context manager will swallow any exceptions inside a TaskGroup
|
| 66 |
# so we return the raw result and raise an exception ourselves
|
| 67 |
async with self._client:
|
| 68 |
-
result = await self._client.
|
| 69 |
-
self.name,
|
|
|
|
| 70 |
)
|
| 71 |
if result.isError:
|
| 72 |
raise ValueError(cast(mcp.types.TextContent, result.content[0]).text)
|
|
|
|
| 65 |
# the client context manager will swallow any exceptions inside a TaskGroup
|
| 66 |
# so we return the raw result and raise an exception ourselves
|
| 67 |
async with self._client:
|
| 68 |
+
result = await self._client.call_tool_mcp(
|
| 69 |
+
name=self.name,
|
| 70 |
+
arguments=arguments,
|
| 71 |
)
|
| 72 |
if result.isError:
|
| 73 |
raise ValueError(cast(mcp.types.TextContent, result.content[0]).text)
|
tests/client/test_client.py
CHANGED
|
@@ -79,6 +79,19 @@ async def test_list_tools(fastmcp_server):
|
|
| 79 |
assert set(tool.name for tool in result) == {"greet", "add"}
|
| 80 |
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
async def test_call_tool(fastmcp_server):
|
| 83 |
"""Test calling a tool with InMemoryClient."""
|
| 84 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
@@ -91,6 +104,26 @@ async def test_call_tool(fastmcp_server):
|
|
| 91 |
assert "Hello, World!" in content_str
|
| 92 |
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
async def test_list_resources(fastmcp_server):
|
| 95 |
"""Test listing resources with InMemoryClient."""
|
| 96 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
@@ -103,6 +136,19 @@ async def test_list_resources(fastmcp_server):
|
|
| 103 |
assert str(result[0].uri) == "data://users"
|
| 104 |
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
async def test_list_prompts(fastmcp_server):
|
| 107 |
"""Test listing prompts with InMemoryClient."""
|
| 108 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
@@ -115,6 +161,19 @@ async def test_list_prompts(fastmcp_server):
|
|
| 115 |
assert result[0].name == "welcome"
|
| 116 |
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
async def test_get_prompt(fastmcp_server):
|
| 119 |
"""Test getting a prompt with InMemoryClient."""
|
| 120 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
@@ -127,6 +186,20 @@ async def test_get_prompt(fastmcp_server):
|
|
| 127 |
assert "Welcome to FastMCP, Developer!" in result_str
|
| 128 |
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
async def test_read_resource(fastmcp_server):
|
| 131 |
"""Test reading a resource with InMemoryClient."""
|
| 132 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
@@ -145,6 +218,26 @@ async def test_read_resource(fastmcp_server):
|
|
| 145 |
assert "Charlie" in contents_str
|
| 146 |
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
async def test_client_connection(fastmcp_server):
|
| 149 |
"""Test that the client connects and disconnects properly."""
|
| 150 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
@@ -213,6 +306,19 @@ async def test_resource_template(fastmcp_server):
|
|
| 213 |
assert '"active": true' in content_str
|
| 214 |
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
async def test_mcp_resource_generation(fastmcp_server):
|
| 217 |
"""Test that resources are properly generated in MCP format."""
|
| 218 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 79 |
assert set(tool.name for tool in result) == {"greet", "add"}
|
| 80 |
|
| 81 |
|
| 82 |
+
async def test_list_tools_mcp(fastmcp_server):
|
| 83 |
+
"""Test the list_tools_mcp method that returns raw MCP protocol objects."""
|
| 84 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 85 |
+
|
| 86 |
+
async with client:
|
| 87 |
+
result = await client.list_tools_mcp()
|
| 88 |
+
|
| 89 |
+
# Check that we got the raw MCP ListToolsResult object
|
| 90 |
+
assert hasattr(result, "tools")
|
| 91 |
+
assert len(result.tools) == 2
|
| 92 |
+
assert set(tool.name for tool in result.tools) == {"greet", "add"}
|
| 93 |
+
|
| 94 |
+
|
| 95 |
async def test_call_tool(fastmcp_server):
|
| 96 |
"""Test calling a tool with InMemoryClient."""
|
| 97 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 104 |
assert "Hello, World!" in content_str
|
| 105 |
|
| 106 |
|
| 107 |
+
async def test_call_tool_mcp(fastmcp_server):
|
| 108 |
+
"""Test the call_tool_mcp method that returns raw MCP protocol objects."""
|
| 109 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 110 |
+
|
| 111 |
+
async with client:
|
| 112 |
+
result = await client.call_tool_mcp("greet", {"name": "World"})
|
| 113 |
+
|
| 114 |
+
# Check that we got the raw MCP CallToolResult object
|
| 115 |
+
assert hasattr(result, "content")
|
| 116 |
+
assert hasattr(result, "isError")
|
| 117 |
+
assert result.isError is False
|
| 118 |
+
# The content is a list, so we'll check the first element
|
| 119 |
+
# by properly accessing it
|
| 120 |
+
content = result.content
|
| 121 |
+
assert len(content) > 0
|
| 122 |
+
first_content = content[0]
|
| 123 |
+
content_str = str(first_content)
|
| 124 |
+
assert "Hello, World!" in content_str
|
| 125 |
+
|
| 126 |
+
|
| 127 |
async def test_list_resources(fastmcp_server):
|
| 128 |
"""Test listing resources with InMemoryClient."""
|
| 129 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 136 |
assert str(result[0].uri) == "data://users"
|
| 137 |
|
| 138 |
|
| 139 |
+
async def test_list_resources_mcp(fastmcp_server):
|
| 140 |
+
"""Test the list_resources_mcp method that returns raw MCP protocol objects."""
|
| 141 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 142 |
+
|
| 143 |
+
async with client:
|
| 144 |
+
result = await client.list_resources_mcp()
|
| 145 |
+
|
| 146 |
+
# Check that we got the raw MCP ListResourcesResult object
|
| 147 |
+
assert hasattr(result, "resources")
|
| 148 |
+
assert len(result.resources) == 1
|
| 149 |
+
assert str(result.resources[0].uri) == "data://users"
|
| 150 |
+
|
| 151 |
+
|
| 152 |
async def test_list_prompts(fastmcp_server):
|
| 153 |
"""Test listing prompts with InMemoryClient."""
|
| 154 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 161 |
assert result[0].name == "welcome"
|
| 162 |
|
| 163 |
|
| 164 |
+
async def test_list_prompts_mcp(fastmcp_server):
|
| 165 |
+
"""Test the list_prompts_mcp method that returns raw MCP protocol objects."""
|
| 166 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 167 |
+
|
| 168 |
+
async with client:
|
| 169 |
+
result = await client.list_prompts_mcp()
|
| 170 |
+
|
| 171 |
+
# Check that we got the raw MCP ListPromptsResult object
|
| 172 |
+
assert hasattr(result, "prompts")
|
| 173 |
+
assert len(result.prompts) == 1
|
| 174 |
+
assert result.prompts[0].name == "welcome"
|
| 175 |
+
|
| 176 |
+
|
| 177 |
async def test_get_prompt(fastmcp_server):
|
| 178 |
"""Test getting a prompt with InMemoryClient."""
|
| 179 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 186 |
assert "Welcome to FastMCP, Developer!" in result_str
|
| 187 |
|
| 188 |
|
| 189 |
+
async def test_get_prompt_mcp(fastmcp_server):
|
| 190 |
+
"""Test the get_prompt_mcp method that returns raw MCP protocol objects."""
|
| 191 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 192 |
+
|
| 193 |
+
async with client:
|
| 194 |
+
result = await client.get_prompt_mcp("welcome", {"name": "Developer"})
|
| 195 |
+
|
| 196 |
+
# Check that we got the raw MCP GetPromptResult object
|
| 197 |
+
assert hasattr(result, "messages")
|
| 198 |
+
assert len(result.messages) > 0
|
| 199 |
+
result_str = str(result.messages)
|
| 200 |
+
assert "Welcome to FastMCP, Developer!" in result_str
|
| 201 |
+
|
| 202 |
+
|
| 203 |
async def test_read_resource(fastmcp_server):
|
| 204 |
"""Test reading a resource with InMemoryClient."""
|
| 205 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 218 |
assert "Charlie" in contents_str
|
| 219 |
|
| 220 |
|
| 221 |
+
async def test_read_resource_mcp(fastmcp_server):
|
| 222 |
+
"""Test the read_resource_mcp method that returns raw MCP protocol objects."""
|
| 223 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 224 |
+
|
| 225 |
+
async with client:
|
| 226 |
+
# Use the URI from the resource we know exists in our server
|
| 227 |
+
uri = cast(
|
| 228 |
+
AnyUrl, "data://users"
|
| 229 |
+
) # Use cast for type hint only, the URI is valid
|
| 230 |
+
result = await client.read_resource_mcp(uri)
|
| 231 |
+
|
| 232 |
+
# Check that we got the raw MCP ReadResourceResult object
|
| 233 |
+
assert hasattr(result, "contents")
|
| 234 |
+
assert len(result.contents) > 0
|
| 235 |
+
contents_str = str(result.contents[0])
|
| 236 |
+
assert "Alice" in contents_str
|
| 237 |
+
assert "Bob" in contents_str
|
| 238 |
+
assert "Charlie" in contents_str
|
| 239 |
+
|
| 240 |
+
|
| 241 |
async def test_client_connection(fastmcp_server):
|
| 242 |
"""Test that the client connects and disconnects properly."""
|
| 243 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
|
|
|
| 306 |
assert '"active": true' in content_str
|
| 307 |
|
| 308 |
|
| 309 |
+
async def test_list_resource_templates_mcp(fastmcp_server):
|
| 310 |
+
"""Test the list_resource_templates_mcp method that returns raw MCP protocol objects."""
|
| 311 |
+
client = Client(transport=FastMCPTransport(fastmcp_server))
|
| 312 |
+
|
| 313 |
+
async with client:
|
| 314 |
+
result = await client.list_resource_templates_mcp()
|
| 315 |
+
|
| 316 |
+
# Check that we got the raw MCP ListResourceTemplatesResult object
|
| 317 |
+
assert hasattr(result, "resourceTemplates")
|
| 318 |
+
assert len(result.resourceTemplates) == 1
|
| 319 |
+
assert "data://user/{user_id}" in result.resourceTemplates[0].uriTemplate
|
| 320 |
+
|
| 321 |
+
|
| 322 |
async def test_mcp_resource_generation(fastmcp_server):
|
| 323 |
"""Test that resources are properly generated in MCP format."""
|
| 324 |
client = Client(transport=FastMCPTransport(fastmcp_server))
|
tests/server/test_server_interactions.py
CHANGED
|
@@ -96,7 +96,7 @@ class TestTools:
|
|
| 96 |
|
| 97 |
async def test_call_tool_error_as_client_raw(self, tool_server: FastMCP):
|
| 98 |
async with Client(tool_server) as client:
|
| 99 |
-
result = await client.
|
| 100 |
assert result.isError
|
| 101 |
assert isinstance(result.content[0], TextContent)
|
| 102 |
assert "Test error" in result.content[0].text
|
|
|
|
| 96 |
|
| 97 |
async def test_call_tool_error_as_client_raw(self, tool_server: FastMCP):
|
| 98 |
async with Client(tool_server) as client:
|
| 99 |
+
result = await client.call_tool_mcp("error_tool", {})
|
| 100 |
assert result.isError
|
| 101 |
assert isinstance(result.content[0], TextContent)
|
| 102 |
assert "Test error" in result.content[0].text
|