Jeremiah Lowin commited on
Commit
5698663
·
1 Parent(s): fba5656

Clean up server tests

Browse files
src/fastmcp/server/server.py CHANGED
@@ -3,7 +3,7 @@
3
  import inspect
4
  import json
5
  import re
6
- from collections.abc import AsyncIterator, Callable, Sequence
7
  from contextlib import (
8
  AbstractAsyncContextManager,
9
  asynccontextmanager,
@@ -179,7 +179,7 @@ class FastMCP(Generic[LifespanResultT]):
179
 
180
  async def call_tool(
181
  self, name: str, arguments: dict[str, Any]
182
- ) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
183
  """Call a tool by name with arguments."""
184
  context = self.get_context()
185
  result = await self._tool_manager.call_tool(name, arguments, context=context)
 
3
  import inspect
4
  import json
5
  import re
6
+ from collections.abc import AsyncIterator, Callable
7
  from contextlib import (
8
  AbstractAsyncContextManager,
9
  asynccontextmanager,
 
179
 
180
  async def call_tool(
181
  self, name: str, arguments: dict[str, Any]
182
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
183
  """Call a tool by name with arguments."""
184
  context = self.get_context()
185
  result = await self._tool_manager.call_tool(name, arguments, context=context)
tests/server/test_server.py CHANGED
@@ -5,9 +5,6 @@ from typing import TYPE_CHECKING
5
 
6
  import pytest
7
  from mcp.shared.exceptions import McpError
8
- from mcp.shared.memory import (
9
- create_connected_server_and_client_session as client_session,
10
- )
11
  from mcp.types import (
12
  BlobResourceContents,
13
  ImageContent,
@@ -16,7 +13,8 @@ from mcp.types import (
16
  )
17
  from pydantic import AnyUrl, Field
18
 
19
- from fastmcp import Context, FastMCP
 
20
  from fastmcp.prompts.prompt import EmbeddedResource, Message, UserMessage
21
  from fastmcp.resources import FileResource, FunctionResource
22
  from fastmcp.utilities.types import Image
@@ -25,7 +23,7 @@ if TYPE_CHECKING:
25
  from fastmcp import Context
26
 
27
 
28
- class TestServer:
29
  async def test_create_server(self):
30
  mcp = FastMCP(instructions="Server instructions")
31
  assert mcp.name == "FastMCP"
@@ -43,18 +41,18 @@ class TestServer:
43
  def hello_world(name: str = "世界") -> str:
44
  return f"¡Hola, {name}! 👋"
45
 
46
- async with client_session(mcp._mcp_server) as client:
47
  tools = await client.list_tools()
48
- assert len(tools.tools) == 1
49
- tool = tools.tools[0]
50
  assert tool.description is not None
51
  assert "🌟" in tool.description
52
  assert "漢字" in tool.description
53
  assert "🎉" in tool.description
54
 
55
  result = await client.call_tool("hello_world", {})
56
- assert len(result.content) == 1
57
- content = result.content[0]
58
  assert isinstance(content, TextContent)
59
  assert "¡Hola, 世界! 👋" == content.text
60
 
@@ -97,171 +95,135 @@ class TestServer:
97
  return f"Data: {x}"
98
 
99
 
100
- def tool_fn(x: int, y: int) -> int:
101
- return x + y
102
-
103
 
104
- def tool_fn_list() -> list[str | int]:
105
- return ["x", 2]
 
106
 
 
 
 
107
 
108
- def error_tool_fn() -> None:
109
- raise ValueError("Test error")
 
110
 
 
 
 
111
 
112
- def image_tool_fn(path: str) -> Image:
113
- return Image(path)
 
 
 
 
114
 
 
 
 
 
 
 
 
 
115
 
116
- def mixed_content_tool_fn() -> list[TextContent | ImageContent]:
117
- return [
118
- TextContent(type="text", text="Hello"),
119
- ImageContent(type="image", data="abc", mimeType="image/png"),
120
- ]
121
 
122
 
123
  class TestServerTools:
124
- async def test_add_tool(self):
125
- mcp = FastMCP()
126
- mcp.add_tool(tool_fn)
127
- mcp.add_tool(tool_fn)
128
- assert len(mcp._tool_manager.list_tools()) == 1
129
-
130
- async def test_list_tools(self):
131
- mcp = FastMCP()
132
- mcp.add_tool(tool_fn)
133
- async with client_session(mcp._mcp_server) as client:
134
- tools = await client.list_tools()
135
- assert len(tools.tools) == 1
136
-
137
- async def test_call_tool(self):
138
- mcp = FastMCP()
139
- mcp.add_tool(tool_fn)
140
- async with client_session(mcp._mcp_server) as client:
141
- result = await client.call_tool("my_tool", {"arg1": "value"})
142
- assert not hasattr(result, "error")
143
- assert len(result.content) > 0
144
-
145
- async def test_tool_exception_handling(self):
146
- mcp = FastMCP()
147
- mcp.add_tool(error_tool_fn)
148
- async with client_session(mcp._mcp_server) as client:
149
- result = await client.call_tool("error_tool_fn", {})
150
- assert len(result.content) == 1
151
- content = result.content[0]
152
- assert isinstance(content, TextContent)
153
- assert "Test error" in content.text
154
- assert result.isError is True
155
-
156
- async def test_tool_error_handling(self):
157
- mcp = FastMCP()
158
- mcp.add_tool(error_tool_fn)
159
- async with client_session(mcp._mcp_server) as client:
160
- result = await client.call_tool("error_tool_fn", {})
161
- assert len(result.content) == 1
162
- content = result.content[0]
163
- assert isinstance(content, TextContent)
164
- assert "Test error" in content.text
165
- assert result.isError is True
166
-
167
- async def test_tool_error_details(self):
168
- """Test that exception details are properly formatted in the response"""
169
- mcp = FastMCP()
170
- mcp.add_tool(error_tool_fn)
171
- async with client_session(mcp._mcp_server) as client:
172
- result = await client.call_tool("error_tool_fn", {})
173
- content = result.content[0]
174
- assert isinstance(content, TextContent)
175
- assert isinstance(content.text, str)
176
- assert "Test error" in content.text
177
- assert result.isError is True
178
-
179
- async def test_tool_return_value_conversion(self):
180
- mcp = FastMCP()
181
- mcp.add_tool(tool_fn)
182
- async with client_session(mcp._mcp_server) as client:
183
- result = await client.call_tool("tool_fn", {"x": 1, "y": 2})
184
- assert len(result.content) == 1
185
- content = result.content[0]
186
- assert isinstance(content, TextContent)
187
- assert content.text == "3"
188
-
189
- async def test_tool_returns_list(self):
190
- mcp = FastMCP()
191
- mcp.add_tool(tool_fn_list)
192
- async with client_session(mcp._mcp_server) as client:
193
- result = await client.call_tool("tool_fn_list", {})
194
- assert len(result.content) == 1
195
- content = result.content[0]
196
- assert isinstance(content, TextContent)
197
- assert json.loads(content.text) == ["x", 2]
198
-
199
- async def test_tool_image_helper(self, tmp_path: Path):
200
  # Create a test image
201
  image_path = tmp_path / "test.png"
202
  image_path.write_bytes(b"fake png data")
203
 
204
- mcp = FastMCP()
205
- mcp.add_tool(image_tool_fn)
206
- async with client_session(mcp._mcp_server) as client:
207
- result = await client.call_tool("image_tool_fn", {"path": str(image_path)})
208
- assert len(result.content) == 1
209
- content = result.content[0]
210
- assert isinstance(content, ImageContent)
211
- assert content.type == "image"
212
- assert content.mimeType == "image/png"
213
- # Verify base64 encoding
214
- decoded = base64.b64decode(content.data)
215
- assert decoded == b"fake png data"
216
-
217
- async def test_tool_mixed_content(self):
218
- mcp = FastMCP()
219
- mcp.add_tool(mixed_content_tool_fn)
220
- async with client_session(mcp._mcp_server) as client:
221
- result = await client.call_tool("mixed_content_tool_fn", {})
222
-
223
- assert len(result.content) == 2
224
- content1 = result.content[0]
225
- content2 = result.content[1]
226
- assert isinstance(content1, TextContent)
227
- assert content1.text == "Hello"
228
- assert isinstance(content2, ImageContent)
229
- assert content2.mimeType == "image/png"
230
- assert content2.data == "abc"
231
-
232
- async def test_tool_mixed_list_with_image(self, tmp_path: Path):
233
  """Test that lists containing Image objects and other types are handled
234
  correctly. Note that the non-MCP content will be grouped together."""
235
  # Create a test image
236
  image_path = tmp_path / "test.png"
237
  image_path.write_bytes(b"test image data")
238
 
239
- def mixed_list_fn() -> list:
240
- return [
241
- "text message",
242
- Image(image_path),
243
- {"key": "value"},
244
- TextContent(type="text", text="direct content"),
245
- ]
246
-
247
- mcp = FastMCP()
248
- mcp.add_tool(mixed_list_fn)
249
- async with client_session(mcp._mcp_server) as client:
250
- result = await client.call_tool("mixed_list_fn", {})
251
- assert len(result.content) == 3
252
- # Check text conversion
253
- content1 = result.content[0]
254
- assert isinstance(content1, TextContent)
255
- assert json.loads(content1.text) == ["text message", {"key": "value"}]
256
- # Check image conversion
257
- content2 = result.content[1]
258
- assert isinstance(content2, ImageContent)
259
- assert content2.mimeType == "image/png"
260
- assert base64.b64decode(content2.data) == b"test image data"
261
- # Check direct TextContent
262
- content3 = result.content[2]
263
- assert isinstance(content3, TextContent)
264
- assert content3.text == "direct content"
265
 
266
  async def test_parameter_descriptions(self):
267
  mcp = FastMCP("Test Server")
@@ -298,10 +260,10 @@ class TestServerResources:
298
  )
299
  mcp.add_resource(resource)
300
 
301
- async with client_session(mcp._mcp_server) as client:
302
  result = await client.read_resource(AnyUrl("resource://test"))
303
- assert isinstance(result.contents[0], TextResourceContents)
304
- assert result.contents[0].text == "Hello, world!"
305
 
306
  async def test_binary_resource(self):
307
  mcp = FastMCP()
@@ -317,10 +279,10 @@ class TestServerResources:
317
  )
318
  mcp.add_resource(resource)
319
 
320
- async with client_session(mcp._mcp_server) as client:
321
  result = await client.read_resource(AnyUrl("resource://binary"))
322
- assert isinstance(result.contents[0], BlobResourceContents)
323
- assert result.contents[0].blob == base64.b64encode(b"Binary data").decode()
324
 
325
  async def test_file_resource_text(self, tmp_path: Path):
326
  mcp = FastMCP()
@@ -334,10 +296,10 @@ class TestServerResources:
334
  )
335
  mcp.add_resource(resource)
336
 
337
- async with client_session(mcp._mcp_server) as client:
338
  result = await client.read_resource(AnyUrl("file://test.txt"))
339
- assert isinstance(result.contents[0], TextResourceContents)
340
- assert result.contents[0].text == "Hello from file!"
341
 
342
  async def test_file_resource_binary(self, tmp_path: Path):
343
  mcp = FastMCP()
@@ -354,13 +316,10 @@ class TestServerResources:
354
  )
355
  mcp.add_resource(resource)
356
 
357
- async with client_session(mcp._mcp_server) as client:
358
  result = await client.read_resource(AnyUrl("file://test.bin"))
359
- assert isinstance(result.contents[0], BlobResourceContents)
360
- assert (
361
- result.contents[0].blob
362
- == base64.b64encode(b"Binary file data").decode()
363
- )
364
 
365
 
366
  class TestServerResourceTemplates:
@@ -401,10 +360,10 @@ class TestServerResourceTemplates:
401
  def get_data(name: str) -> str:
402
  return f"Data for {name}"
403
 
404
- async with client_session(mcp._mcp_server) as client:
405
  result = await client.read_resource(AnyUrl("resource://test/data"))
406
- assert isinstance(result.contents[0], TextResourceContents)
407
- assert result.contents[0].text == "Data for test"
408
 
409
  async def test_resource_mismatched_params(self):
410
  """Test that mismatched parameters raise an error"""
@@ -424,12 +383,12 @@ class TestServerResourceTemplates:
424
  def get_data(org: str, repo: str) -> str:
425
  return f"Data for {org}/{repo}"
426
 
427
- async with client_session(mcp._mcp_server) as client:
428
  result = await client.read_resource(
429
  AnyUrl("resource://cursor/fastmcp/data")
430
  )
431
- assert isinstance(result.contents[0], TextResourceContents)
432
- assert result.contents[0].text == "Data for cursor/fastmcp"
433
 
434
  async def test_resource_multiple_mismatched_params(self):
435
  """Test that mismatched parameters raise an error"""
@@ -448,10 +407,10 @@ class TestServerResourceTemplates:
448
  def get_static_data() -> str:
449
  return "Static data"
450
 
451
- async with client_session(mcp._mcp_server) as client:
452
  result = await client.read_resource(AnyUrl("resource://static"))
453
- assert isinstance(result.contents[0], TextResourceContents)
454
- assert result.contents[0].text == "Static data"
455
 
456
  async def test_template_to_resource_conversion(self):
457
  """Test that templates are properly converted to resources when accessed"""
@@ -494,10 +453,10 @@ class TestContextInjection:
494
  return f"Request {ctx.request_id}: {x}"
495
 
496
  mcp.add_tool(tool_with_context)
497
- async with client_session(mcp._mcp_server) as client:
498
  result = await client.call_tool("tool_with_context", {"x": 42})
499
- assert len(result.content) == 1
500
- content = result.content[0]
501
  assert isinstance(content, TextContent)
502
  assert "Request" in content.text
503
  assert "42" in content.text
@@ -511,10 +470,10 @@ class TestContextInjection:
511
  return f"Async request {ctx.request_id}: {x}"
512
 
513
  mcp.add_tool(async_tool)
514
- async with client_session(mcp._mcp_server) as client:
515
  result = await client.call_tool("async_tool", {"x": 42})
516
- assert len(result.content) == 1
517
- content = result.content[0]
518
  assert isinstance(content, TextContent)
519
  assert "Async request" in content.text
520
  assert "42" in content.text
@@ -537,10 +496,10 @@ class TestContextInjection:
537
  mcp.add_tool(logging_tool)
538
 
539
  with patch("mcp.server.session.ServerSession.send_log_message") as mock_log:
540
- async with client_session(mcp._mcp_server) as client:
541
  result = await client.call_tool("logging_tool", {"msg": "test"})
542
- assert len(result.content) == 1
543
- content = result.content[0]
544
  assert isinstance(content, TextContent)
545
  assert "Logged messages for test" in content.text
546
 
@@ -564,10 +523,10 @@ class TestContextInjection:
564
  return x * 2
565
 
566
  mcp.add_tool(no_context)
567
- async with client_session(mcp._mcp_server) as client:
568
  result = await client.call_tool("no_context", {"x": 21})
569
- assert len(result.content) == 1
570
- content = result.content[0]
571
  assert isinstance(content, TextContent)
572
  assert content.text == "42"
573
 
@@ -587,10 +546,10 @@ class TestContextInjection:
587
  r = r_list[0]
588
  return f"Read resource: {r.content} with mime type {r.mime_type}"
589
 
590
- async with client_session(mcp._mcp_server) as client:
591
  result = await client.call_tool("tool_with_resource", {})
592
- assert len(result.content) == 1
593
- content = result.content[0]
594
  assert isinstance(content, TextContent)
595
  assert "Read resource: resource data" in content.text
596
 
@@ -661,11 +620,11 @@ class TestServerPrompts:
661
  def fn(name: str, optional: str = "default") -> str:
662
  return f"Hello, {name}!"
663
 
664
- async with client_session(mcp._mcp_server) as client:
665
  result = await client.list_prompts()
666
- assert result.prompts is not None
667
- assert len(result.prompts) == 1
668
- prompt = result.prompts[0]
669
  assert prompt.name == "fn"
670
  assert prompt.arguments is not None
671
  assert len(prompt.arguments) == 2
@@ -682,7 +641,7 @@ class TestServerPrompts:
682
  def fn(name: str) -> str:
683
  return f"Hello, {name}!"
684
 
685
- async with client_session(mcp._mcp_server) as client:
686
  result = await client.get_prompt("fn", {"name": "World"})
687
  assert len(result.messages) == 1
688
  message = result.messages[0]
@@ -708,12 +667,10 @@ class TestServerPrompts:
708
  )
709
  )
710
 
711
- async with client_session(mcp._mcp_server) as client:
712
  result = await client.get_prompt("fn")
713
- assert len(result.messages) == 1
714
- message = result.messages[0]
715
- assert message.role == "user"
716
- content = message.content
717
  assert isinstance(content, EmbeddedResource)
718
  resource = content.resource
719
  assert isinstance(resource, TextResourceContents)
@@ -723,7 +680,7 @@ class TestServerPrompts:
723
  async def test_get_unknown_prompt(self):
724
  """Test error when getting unknown prompt."""
725
  mcp = FastMCP()
726
- async with client_session(mcp._mcp_server) as client:
727
  with pytest.raises(McpError, match="Unknown prompt"):
728
  await client.get_prompt("unknown")
729
 
@@ -735,7 +692,7 @@ class TestServerPrompts:
735
  def prompt_fn(name: str) -> str:
736
  return f"Hello, {name}!"
737
 
738
- async with client_session(mcp._mcp_server) as client:
739
  with pytest.raises(McpError, match="Missing required arguments"):
740
  await client.get_prompt("prompt_fn")
741
 
 
5
 
6
  import pytest
7
  from mcp.shared.exceptions import McpError
 
 
 
8
  from mcp.types import (
9
  BlobResourceContents,
10
  ImageContent,
 
13
  )
14
  from pydantic import AnyUrl, Field
15
 
16
+ from fastmcp import Client, Context, FastMCP
17
+ from fastmcp.exceptions import ToolError
18
  from fastmcp.prompts.prompt import EmbeddedResource, Message, UserMessage
19
  from fastmcp.resources import FileResource, FunctionResource
20
  from fastmcp.utilities.types import Image
 
23
  from fastmcp import Context
24
 
25
 
26
+ class TestCreateServer:
27
  async def test_create_server(self):
28
  mcp = FastMCP(instructions="Server instructions")
29
  assert mcp.name == "FastMCP"
 
41
  def hello_world(name: str = "世界") -> str:
42
  return f"¡Hola, {name}! 👋"
43
 
44
+ async with Client(mcp) as client:
45
  tools = await client.list_tools()
46
+ assert len(tools) == 1
47
+ tool = tools[0]
48
  assert tool.description is not None
49
  assert "🌟" in tool.description
50
  assert "漢字" in tool.description
51
  assert "🎉" in tool.description
52
 
53
  result = await client.call_tool("hello_world", {})
54
+ assert len(result) == 1
55
+ content = result[0]
56
  assert isinstance(content, TextContent)
57
  assert "¡Hola, 世界! 👋" == content.text
58
 
 
95
  return f"Data: {x}"
96
 
97
 
98
+ @pytest.fixture
99
+ def tool_server():
100
+ mcp = FastMCP()
101
 
102
+ @mcp.tool()
103
+ def add(x: int, y: int) -> int:
104
+ return x + y
105
 
106
+ @mcp.tool()
107
+ def list_tool() -> list[str | int]:
108
+ return ["x", 2]
109
 
110
+ @mcp.tool()
111
+ def error_tool() -> None:
112
+ raise ValueError("Test error")
113
 
114
+ @mcp.tool()
115
+ def image_tool(path: str) -> Image:
116
+ return Image(path)
117
 
118
+ @mcp.tool()
119
+ def mixed_content_tool() -> list[TextContent | ImageContent]:
120
+ return [
121
+ TextContent(type="text", text="Hello"),
122
+ ImageContent(type="image", data="abc", mimeType="image/png"),
123
+ ]
124
 
125
+ @mcp.tool()
126
+ def mixed_list_fn(image_path: str) -> list:
127
+ return [
128
+ "text message",
129
+ Image(image_path),
130
+ {"key": "value"},
131
+ TextContent(type="text", text="direct content"),
132
+ ]
133
 
134
+ return mcp
 
 
 
 
135
 
136
 
137
  class TestServerTools:
138
+ async def test_add_tool_exists(self, tool_server: FastMCP):
139
+ assert "add" in [t.name for t in await tool_server.list_tools()]
140
+
141
+ async def test_list_tools(self, tool_server: FastMCP):
142
+ assert len(await tool_server.list_tools()) == 6
143
+
144
+ async def test_call_tool(self, tool_server: FastMCP):
145
+ result = await tool_server.call_tool("add", {"x": 1, "y": 2})
146
+ assert isinstance(result[0], TextContent)
147
+ assert result[0].text == "3"
148
+
149
+ async def test_call_tool_as_client(self, tool_server: FastMCP):
150
+ async with Client(tool_server) as client:
151
+ result = await client.call_tool("add", {"x": 1, "y": 2})
152
+ assert isinstance(result[0], TextContent)
153
+ assert result[0].text == "3"
154
+
155
+ async def test_call_tool_error(self, tool_server: FastMCP):
156
+ with pytest.raises(ToolError):
157
+ await tool_server.call_tool("error_tool", {})
158
+
159
+ async def test_call_tool_error_as_client(self, tool_server: FastMCP):
160
+ async with Client(tool_server) as client:
161
+ with pytest.raises(Exception):
162
+ await client.call_tool("error_tool", {})
163
+
164
+ async def test_call_tool_error_as_client_raw(self, tool_server: FastMCP):
165
+ async with Client(tool_server) as client:
166
+ result = await client.call_tool("error_tool", {}, _return_raw_result=True)
167
+ assert result.isError
168
+ assert isinstance(result.content[0], TextContent)
169
+ assert "Test error" in result.content[0].text
170
+
171
+ async def test_tool_returns_list(self, tool_server: FastMCP):
172
+ result = await tool_server.call_tool("list_tool", {})
173
+ assert isinstance(result[0], TextContent)
174
+ assert result[0].text == '["x", 2]'
175
+
176
+ async def test_tool_image_helper(self, tool_server: FastMCP, tmp_path: Path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  # Create a test image
178
  image_path = tmp_path / "test.png"
179
  image_path.write_bytes(b"fake png data")
180
 
181
+ result = await tool_server.call_tool("image_tool", {"path": str(image_path)})
182
+ content = result[0]
183
+ assert isinstance(content, ImageContent)
184
+ assert content.type == "image"
185
+ assert content.mimeType == "image/png"
186
+ # Verify base64 encoding
187
+ decoded = base64.b64decode(content.data)
188
+ assert decoded == b"fake png data"
189
+
190
+ async def test_tool_mixed_content(self, tool_server: FastMCP):
191
+ result = await tool_server.call_tool("mixed_content_tool", {})
192
+ assert len(result) == 2
193
+ content1 = result[0]
194
+ content2 = result[1]
195
+ assert isinstance(content1, TextContent)
196
+ assert content1.text == "Hello"
197
+ assert isinstance(content2, ImageContent)
198
+ assert content2.mimeType == "image/png"
199
+ assert content2.data == "abc"
200
+
201
+ async def test_tool_mixed_list_with_image(
202
+ self, tool_server: FastMCP, tmp_path: Path
203
+ ):
 
 
 
 
 
 
204
  """Test that lists containing Image objects and other types are handled
205
  correctly. Note that the non-MCP content will be grouped together."""
206
  # Create a test image
207
  image_path = tmp_path / "test.png"
208
  image_path.write_bytes(b"test image data")
209
 
210
+ result = await tool_server.call_tool(
211
+ "mixed_list_fn", {"image_path": str(image_path)}
212
+ )
213
+ assert len(result) == 3
214
+ # Check text conversion
215
+ content1 = result[0]
216
+ assert isinstance(content1, TextContent)
217
+ assert json.loads(content1.text) == ["text message", {"key": "value"}]
218
+ # Check image conversion
219
+ content2 = result[1]
220
+ assert isinstance(content2, ImageContent)
221
+ assert content2.mimeType == "image/png"
222
+ assert base64.b64decode(content2.data) == b"test image data"
223
+ # Check direct TextContent
224
+ content3 = result[2]
225
+ assert isinstance(content3, TextContent)
226
+ assert content3.text == "direct content"
 
 
 
 
 
 
 
 
 
227
 
228
  async def test_parameter_descriptions(self):
229
  mcp = FastMCP("Test Server")
 
260
  )
261
  mcp.add_resource(resource)
262
 
263
+ async with Client(mcp) as client:
264
  result = await client.read_resource(AnyUrl("resource://test"))
265
+ assert isinstance(result[0], TextResourceContents)
266
+ assert result[0].text == "Hello, world!"
267
 
268
  async def test_binary_resource(self):
269
  mcp = FastMCP()
 
279
  )
280
  mcp.add_resource(resource)
281
 
282
+ async with Client(mcp) as client:
283
  result = await client.read_resource(AnyUrl("resource://binary"))
284
+ assert isinstance(result[0], BlobResourceContents)
285
+ assert result[0].blob == base64.b64encode(b"Binary data").decode()
286
 
287
  async def test_file_resource_text(self, tmp_path: Path):
288
  mcp = FastMCP()
 
296
  )
297
  mcp.add_resource(resource)
298
 
299
+ async with Client(mcp) as client:
300
  result = await client.read_resource(AnyUrl("file://test.txt"))
301
+ assert isinstance(result[0], TextResourceContents)
302
+ assert result[0].text == "Hello from file!"
303
 
304
  async def test_file_resource_binary(self, tmp_path: Path):
305
  mcp = FastMCP()
 
316
  )
317
  mcp.add_resource(resource)
318
 
319
+ async with Client(mcp) as client:
320
  result = await client.read_resource(AnyUrl("file://test.bin"))
321
+ assert isinstance(result[0], BlobResourceContents)
322
+ assert result[0].blob == base64.b64encode(b"Binary file data").decode()
 
 
 
323
 
324
 
325
  class TestServerResourceTemplates:
 
360
  def get_data(name: str) -> str:
361
  return f"Data for {name}"
362
 
363
+ async with Client(mcp) as client:
364
  result = await client.read_resource(AnyUrl("resource://test/data"))
365
+ assert isinstance(result[0], TextResourceContents)
366
+ assert result[0].text == "Data for test"
367
 
368
  async def test_resource_mismatched_params(self):
369
  """Test that mismatched parameters raise an error"""
 
383
  def get_data(org: str, repo: str) -> str:
384
  return f"Data for {org}/{repo}"
385
 
386
+ async with Client(mcp) as client:
387
  result = await client.read_resource(
388
  AnyUrl("resource://cursor/fastmcp/data")
389
  )
390
+ assert isinstance(result[0], TextResourceContents)
391
+ assert result[0].text == "Data for cursor/fastmcp"
392
 
393
  async def test_resource_multiple_mismatched_params(self):
394
  """Test that mismatched parameters raise an error"""
 
407
  def get_static_data() -> str:
408
  return "Static data"
409
 
410
+ async with Client(mcp) as client:
411
  result = await client.read_resource(AnyUrl("resource://static"))
412
+ assert isinstance(result[0], TextResourceContents)
413
+ assert result[0].text == "Static data"
414
 
415
  async def test_template_to_resource_conversion(self):
416
  """Test that templates are properly converted to resources when accessed"""
 
453
  return f"Request {ctx.request_id}: {x}"
454
 
455
  mcp.add_tool(tool_with_context)
456
+ async with Client(mcp) as client:
457
  result = await client.call_tool("tool_with_context", {"x": 42})
458
+ assert len(result) == 1
459
+ content = result[0]
460
  assert isinstance(content, TextContent)
461
  assert "Request" in content.text
462
  assert "42" in content.text
 
470
  return f"Async request {ctx.request_id}: {x}"
471
 
472
  mcp.add_tool(async_tool)
473
+ async with Client(mcp) as client:
474
  result = await client.call_tool("async_tool", {"x": 42})
475
+ assert len(result) == 1
476
+ content = result[0]
477
  assert isinstance(content, TextContent)
478
  assert "Async request" in content.text
479
  assert "42" in content.text
 
496
  mcp.add_tool(logging_tool)
497
 
498
  with patch("mcp.server.session.ServerSession.send_log_message") as mock_log:
499
+ async with Client(mcp) as client:
500
  result = await client.call_tool("logging_tool", {"msg": "test"})
501
+ assert len(result) == 1
502
+ content = result[0]
503
  assert isinstance(content, TextContent)
504
  assert "Logged messages for test" in content.text
505
 
 
523
  return x * 2
524
 
525
  mcp.add_tool(no_context)
526
+ async with Client(mcp) as client:
527
  result = await client.call_tool("no_context", {"x": 21})
528
+ assert len(result) == 1
529
+ content = result[0]
530
  assert isinstance(content, TextContent)
531
  assert content.text == "42"
532
 
 
546
  r = r_list[0]
547
  return f"Read resource: {r.content} with mime type {r.mime_type}"
548
 
549
+ async with Client(mcp) as client:
550
  result = await client.call_tool("tool_with_resource", {})
551
+ assert len(result) == 1
552
+ content = result[0]
553
  assert isinstance(content, TextContent)
554
  assert "Read resource: resource data" in content.text
555
 
 
620
  def fn(name: str, optional: str = "default") -> str:
621
  return f"Hello, {name}!"
622
 
623
+ async with Client(mcp) as client:
624
  result = await client.list_prompts()
625
+ assert result is not None
626
+ assert len(result) == 1
627
+ prompt = result[0]
628
  assert prompt.name == "fn"
629
  assert prompt.arguments is not None
630
  assert len(prompt.arguments) == 2
 
641
  def fn(name: str) -> str:
642
  return f"Hello, {name}!"
643
 
644
+ async with Client(mcp) as client:
645
  result = await client.get_prompt("fn", {"name": "World"})
646
  assert len(result.messages) == 1
647
  message = result.messages[0]
 
667
  )
668
  )
669
 
670
+ async with Client(mcp) as client:
671
  result = await client.get_prompt("fn")
672
+ assert result.messages[0].role == "user"
673
+ content = result.messages[0].content
 
 
674
  assert isinstance(content, EmbeddedResource)
675
  resource = content.resource
676
  assert isinstance(resource, TextResourceContents)
 
680
  async def test_get_unknown_prompt(self):
681
  """Test error when getting unknown prompt."""
682
  mcp = FastMCP()
683
+ async with Client(mcp) as client:
684
  with pytest.raises(McpError, match="Unknown prompt"):
685
  await client.get_prompt("unknown")
686
 
 
692
  def prompt_fn(name: str) -> str:
693
  return f"Hello, {name}!"
694
 
695
+ async with Client(mcp) as client:
696
  with pytest.raises(McpError, match="Missing required arguments"):
697
  await client.get_prompt("prompt_fn")
698