Jeremiah Lowin commited on
Commit
c673fcb
·
1 Parent(s): 06a8bd5

test server updates

Browse files
Files changed (1) hide show
  1. tests/test_server.py +143 -80
tests/test_server.py CHANGED
@@ -7,7 +7,13 @@ from mcp.shared.exceptions import McpError
7
  from mcp.shared.memory import (
8
  create_connected_server_and_client_session as client_session,
9
  )
10
- from mcp.types import ImageContent, TextContent
 
 
 
 
 
 
11
 
12
  from fastmcp import Context, FastMCP
13
  from fastmcp.prompts.base import EmbeddedResource, Message, UserMessage
@@ -100,7 +106,7 @@ class TestServerTools:
100
  mcp.add_tool(tool_fn)
101
  async with client_session(mcp._mcp_server) as client:
102
  result = await client.call_tool("my_tool", {"arg1": "value"})
103
- assert "error" not in result
104
  assert len(result.content) > 0
105
 
106
  async def test_tool_exception_handling(self):
@@ -109,29 +115,43 @@ class TestServerTools:
109
  async with client_session(mcp._mcp_server) as client:
110
  result = await client.call_tool("error_tool_fn", {})
111
  assert len(result.content) == 1
112
- assert result.content[0].type == "text"
113
- assert "Test error" in result.content[0].text
 
 
 
 
 
 
 
 
 
 
 
 
114
  assert result.isError is True
115
 
116
- async def test_tool_exception_content(self):
117
  """Test that exception details are properly formatted in the response"""
118
  mcp = FastMCP()
119
  mcp.add_tool(error_tool_fn)
120
  async with client_session(mcp._mcp_server) as client:
121
  result = await client.call_tool("error_tool_fn", {})
122
- assert result.content[0].type == "text"
123
- assert isinstance(result.content[0].text, str)
124
- assert "Test error" in result.content[0].text
 
125
  assert result.isError is True
126
 
127
- async def test_tool_text_conversion(self):
128
  mcp = FastMCP()
129
  mcp.add_tool(tool_fn)
130
  async with client_session(mcp._mcp_server) as client:
131
  result = await client.call_tool("tool_fn", {"x": 1, "y": 2})
132
  assert len(result.content) == 1
133
- assert result.content[0].type == "text"
134
- assert result.content[0].text == "3"
 
135
 
136
  async def test_tool_image_helper(self, tmp_path: Path):
137
  # Create a test image
@@ -143,10 +163,12 @@ class TestServerTools:
143
  async with client_session(mcp._mcp_server) as client:
144
  result = await client.call_tool("image_tool_fn", {"path": str(image_path)})
145
  assert len(result.content) == 1
146
- assert result.content[0].type == "image"
147
- assert result.content[0].mimeType == "image/png"
 
 
148
  # Verify base64 encoding
149
- decoded = base64.b64decode(result.content[0].data)
150
  assert decoded == b"fake png data"
151
 
152
  async def test_tool_mixed_content(self):
@@ -155,11 +177,13 @@ class TestServerTools:
155
  async with client_session(mcp._mcp_server) as client:
156
  result = await client.call_tool("mixed_content_tool_fn", {})
157
  assert len(result.content) == 2
158
- assert result.content[0].type == "text"
159
- assert result.content[0].text == "Hello"
160
- assert result.content[1].type == "image"
161
- assert result.content[1].mimeType == "image/png"
162
- assert result.content[1].data == "abc"
 
 
163
 
164
  async def test_tool_mixed_list_with_image(self, tmp_path: Path):
165
  """Test that lists containing Image objects and other types are handled correctly"""
@@ -181,18 +205,22 @@ class TestServerTools:
181
  result = await client.call_tool("mixed_list_fn", {})
182
  assert len(result.content) == 4
183
  # Check text conversion
184
- assert result.content[0].type == "text"
185
- assert "text message" in result.content[0].text
 
186
  # Check image conversion
187
- assert result.content[1].type == "image"
188
- assert result.content[1].mimeType == "image/png"
189
- assert base64.b64decode(result.content[1].data) == b"test image data"
 
190
  # Check dict conversion
191
- assert result.content[2].type == "text"
192
- assert '"key": "value"' in result.content[2].text
 
193
  # Check direct TextContent
194
- assert result.content[3].type == "text"
195
- assert result.content[3].text == "direct content"
 
196
 
197
 
198
  class TestServerResources:
@@ -202,11 +230,14 @@ class TestServerResources:
202
  def get_text():
203
  return "Hello, world!"
204
 
205
- resource = FunctionResource(uri="resource://test", name="test", fn=get_text)
 
 
206
  mcp.add_resource(resource)
207
 
208
  async with client_session(mcp._mcp_server) as client:
209
- result = await client.read_resource("resource://test")
 
210
  assert result.contents[0].text == "Hello, world!"
211
 
212
  async def test_binary_resource(self):
@@ -216,16 +247,16 @@ class TestServerResources:
216
  return b"Binary data"
217
 
218
  resource = FunctionResource(
219
- uri="resource://binary",
220
  name="binary",
221
  fn=get_binary,
222
- is_binary=True,
223
  mime_type="application/octet-stream",
224
  )
225
  mcp.add_resource(resource)
226
 
227
  async with client_session(mcp._mcp_server) as client:
228
- result = await client.read_resource("resource://binary")
 
229
  assert result.contents[0].blob == base64.b64encode(b"Binary data").decode()
230
 
231
  async def test_file_resource_text(self, tmp_path: Path):
@@ -235,11 +266,14 @@ class TestServerResources:
235
  text_file = tmp_path / "test.txt"
236
  text_file.write_text("Hello from file!")
237
 
238
- resource = FileResource(uri="file://test.txt", name="test.txt", path=text_file)
 
 
239
  mcp.add_resource(resource)
240
 
241
  async with client_session(mcp._mcp_server) as client:
242
- result = await client.read_resource("file://test.txt")
 
243
  assert result.contents[0].text == "Hello from file!"
244
 
245
  async def test_file_resource_binary(self, tmp_path: Path):
@@ -250,16 +284,16 @@ class TestServerResources:
250
  binary_file.write_bytes(b"Binary file data")
251
 
252
  resource = FileResource(
253
- uri="file://test.bin",
254
  name="test.bin",
255
  path=binary_file,
256
- is_binary=True,
257
  mime_type="application/octet-stream",
258
  )
259
  mcp.add_resource(resource)
260
 
261
  async with client_session(mcp._mcp_server) as client:
262
- result = await client.read_resource("file://test.bin")
 
263
  assert (
264
  result.contents[0].blob
265
  == base64.b64encode(b"Binary file data").decode()
@@ -275,7 +309,7 @@ class TestServerResourceTemplates:
275
  with pytest.raises(ValueError, match="Mismatch between URI parameters"):
276
 
277
  @mcp.resource("resource://data")
278
- def get_data(param: str) -> str:
279
  return f"Data: {param}"
280
 
281
  async def test_resource_with_uri_params(self):
@@ -305,7 +339,8 @@ class TestServerResourceTemplates:
305
  return f"Data for {name}"
306
 
307
  async with client_session(mcp._mcp_server) as client:
308
- result = await client.read_resource("resource://test/data")
 
309
  assert result.contents[0].text == "Data for test"
310
 
311
  async def test_resource_mismatched_params(self):
@@ -327,7 +362,10 @@ class TestServerResourceTemplates:
327
  return f"Data for {org}/{repo}"
328
 
329
  async with client_session(mcp._mcp_server) as client:
330
- result = await client.read_resource("resource://cursor/fastmcp/data")
 
 
 
331
  assert result.contents[0].text == "Data for cursor/fastmcp"
332
 
333
  async def test_resource_multiple_mismatched_params(self):
@@ -337,18 +375,19 @@ class TestServerResourceTemplates:
337
  with pytest.raises(ValueError, match="Mismatch between URI parameters"):
338
 
339
  @mcp.resource("resource://{org}/{repo}/data")
340
- def get_data(org: str, repo_2: str) -> str:
341
  return f"Data for {org}"
342
 
343
  """Test that a resource with no parameters works as a regular resource"""
344
  mcp = FastMCP()
345
 
346
  @mcp.resource("resource://static")
347
- def get_data() -> str:
348
  return "Static data"
349
 
350
  async with client_session(mcp._mcp_server) as client:
351
- result = await client.read_resource("resource://static")
 
352
  assert result.contents[0].text == "Static data"
353
 
354
  async def test_template_to_resource_conversion(self):
@@ -395,8 +434,10 @@ class TestContextInjection:
395
  async with client_session(mcp._mcp_server) as client:
396
  result = await client.call_tool("tool_with_context", {"x": 42})
397
  assert len(result.content) == 1
398
- assert "Request" in result.content[0].text
399
- assert "42" in result.content[0].text
 
 
400
 
401
  async def test_async_context(self):
402
  """Test that context works in async functions."""
@@ -410,8 +451,10 @@ class TestContextInjection:
410
  async with client_session(mcp._mcp_server) as client:
411
  result = await client.call_tool("async_tool", {"x": 42})
412
  assert len(result.content) == 1
413
- assert "Async request" in result.content[0].text
414
- assert "42" in result.content[0].text
 
 
415
 
416
  async def test_context_logging(self):
417
  """Test that context logging methods work."""
@@ -428,7 +471,9 @@ class TestContextInjection:
428
  async with client_session(mcp._mcp_server) as client:
429
  result = await client.call_tool("logging_tool", {"msg": "test"})
430
  assert len(result.content) == 1
431
- assert "Logged messages for test" in result.content[0].text
 
 
432
 
433
  async def test_optional_context(self):
434
  """Test that context is optional."""
@@ -441,7 +486,9 @@ class TestContextInjection:
441
  async with client_session(mcp._mcp_server) as client:
442
  result = await client.call_tool("no_context", {"x": 21})
443
  assert len(result.content) == 1
444
- assert result.content[0].text == "42"
 
 
445
 
446
  async def test_context_resource_access(self):
447
  """Test that context can access resources."""
@@ -459,7 +506,9 @@ class TestContextInjection:
459
  async with client_session(mcp._mcp_server) as client:
460
  result = await client.call_tool("tool_with_resource", {})
461
  assert len(result.content) == 1
462
- assert "Read resource: resource data" in result.content[0].text
 
 
463
 
464
 
465
  class TestServerPrompts:
@@ -477,23 +526,26 @@ class TestServerPrompts:
477
  assert len(prompts) == 1
478
  assert prompts[0].name == "fn"
479
  # Don't compare functions directly since validate_call wraps them
480
- assert await prompts[0].render() == [
481
- UserMessage(content=TextContent(type="text", text="Hello, world!"))
482
- ]
483
 
484
- def test_prompt_decorator_with_name(self):
485
  """Test prompt decorator with custom name."""
486
  mcp = FastMCP()
487
 
488
- @mcp.prompt(name="custom")
489
  def fn() -> str:
490
  return "Hello, world!"
491
 
492
  prompts = mcp._prompt_manager.list_prompts()
493
  assert len(prompts) == 1
494
- assert prompts[0].name == "custom"
 
 
 
495
 
496
- def test_prompt_decorator_with_description(self):
497
  """Test prompt decorator with custom description."""
498
  mcp = FastMCP()
499
 
@@ -504,13 +556,16 @@ class TestServerPrompts:
504
  prompts = mcp._prompt_manager.list_prompts()
505
  assert len(prompts) == 1
506
  assert prompts[0].description == "A custom description"
 
 
 
507
 
508
  def test_prompt_decorator_error(self):
509
  """Test error when decorator is used incorrectly."""
510
  mcp = FastMCP()
511
  with pytest.raises(TypeError, match="decorator was used incorrectly"):
512
 
513
- @mcp.prompt
514
  def fn() -> str:
515
  return "Hello, world!"
516
 
@@ -524,13 +579,16 @@ class TestServerPrompts:
524
 
525
  async with client_session(mcp._mcp_server) as client:
526
  result = await client.list_prompts()
 
527
  assert len(result.prompts) == 1
528
- assert result.prompts[0].name == "fn"
529
- assert len(result.prompts[0].arguments) == 2
530
- assert result.prompts[0].arguments[0].name == "name"
531
- assert result.prompts[0].arguments[0].required is True
532
- assert result.prompts[0].arguments[1].name == "optional"
533
- assert result.prompts[0].arguments[1].required is False
 
 
534
 
535
  async def test_get_prompt(self):
536
  """Test getting a prompt through MCP protocol."""
@@ -543,9 +601,11 @@ class TestServerPrompts:
543
  async with client_session(mcp._mcp_server) as client:
544
  result = await client.get_prompt("fn", {"name": "World"})
545
  assert len(result.messages) == 1
546
- assert result.messages[0].role == "user"
547
- assert result.messages[0].content.type == "text"
548
- assert result.messages[0].content.text == "Hello, World!"
 
 
549
 
550
  async def test_get_prompt_with_resource(self):
551
  """Test getting a prompt that returns resource content."""
@@ -556,22 +616,25 @@ class TestServerPrompts:
556
  return UserMessage(
557
  content=EmbeddedResource(
558
  type="resource",
559
- resource={
560
- "uri": "file://test.txt",
561
- "text": "File contents",
562
- "mimeType": "text/plain",
563
- },
564
  )
565
  )
566
 
567
  async with client_session(mcp._mcp_server) as client:
568
  result = await client.get_prompt("fn")
569
  assert len(result.messages) == 1
570
- assert result.messages[0].role == "user"
571
- assert result.messages[0].content.type == "resource"
572
- assert str(result.messages[0].content.resource.uri) == "file://test.txt/"
573
- assert result.messages[0].content.resource.text == "File contents"
574
- assert result.messages[0].content.resource.mimeType == "text/plain"
 
 
 
575
 
576
  async def test_get_unknown_prompt(self):
577
  """Test error when getting unknown prompt."""
@@ -585,9 +648,9 @@ class TestServerPrompts:
585
  mcp = FastMCP()
586
 
587
  @mcp.prompt()
588
- def fn(name: str) -> str:
589
  return f"Hello, {name}!"
590
 
591
  async with client_session(mcp._mcp_server) as client:
592
  with pytest.raises(McpError, match="Missing required arguments"):
593
- await client.get_prompt("fn")
 
7
  from mcp.shared.memory import (
8
  create_connected_server_and_client_session as client_session,
9
  )
10
+ from mcp.types import (
11
+ ImageContent,
12
+ TextContent,
13
+ TextResourceContents,
14
+ BlobResourceContents,
15
+ )
16
+ from pydantic import AnyUrl
17
 
18
  from fastmcp import Context, FastMCP
19
  from fastmcp.prompts.base import EmbeddedResource, Message, UserMessage
 
106
  mcp.add_tool(tool_fn)
107
  async with client_session(mcp._mcp_server) as client:
108
  result = await client.call_tool("my_tool", {"arg1": "value"})
109
+ assert not hasattr(result, "error")
110
  assert len(result.content) > 0
111
 
112
  async def test_tool_exception_handling(self):
 
115
  async with client_session(mcp._mcp_server) as client:
116
  result = await client.call_tool("error_tool_fn", {})
117
  assert len(result.content) == 1
118
+ content = result.content[0]
119
+ assert isinstance(content, TextContent)
120
+ assert "Test error" in content.text
121
+ assert result.isError is True
122
+
123
+ async def test_tool_error_handling(self):
124
+ mcp = FastMCP()
125
+ mcp.add_tool(error_tool_fn)
126
+ async with client_session(mcp._mcp_server) as client:
127
+ result = await client.call_tool("error_tool_fn", {})
128
+ assert len(result.content) == 1
129
+ content = result.content[0]
130
+ assert isinstance(content, TextContent)
131
+ assert "Test error" in content.text
132
  assert result.isError is True
133
 
134
+ async def test_tool_error_details(self):
135
  """Test that exception details are properly formatted in the response"""
136
  mcp = FastMCP()
137
  mcp.add_tool(error_tool_fn)
138
  async with client_session(mcp._mcp_server) as client:
139
  result = await client.call_tool("error_tool_fn", {})
140
+ content = result.content[0]
141
+ assert isinstance(content, TextContent)
142
+ assert isinstance(content.text, str)
143
+ assert "Test error" in content.text
144
  assert result.isError is True
145
 
146
+ async def test_tool_return_value_conversion(self):
147
  mcp = FastMCP()
148
  mcp.add_tool(tool_fn)
149
  async with client_session(mcp._mcp_server) as client:
150
  result = await client.call_tool("tool_fn", {"x": 1, "y": 2})
151
  assert len(result.content) == 1
152
+ content = result.content[0]
153
+ assert isinstance(content, TextContent)
154
+ assert content.text == "3"
155
 
156
  async def test_tool_image_helper(self, tmp_path: Path):
157
  # Create a test image
 
163
  async with client_session(mcp._mcp_server) as client:
164
  result = await client.call_tool("image_tool_fn", {"path": str(image_path)})
165
  assert len(result.content) == 1
166
+ content = result.content[0]
167
+ assert isinstance(content, ImageContent)
168
+ assert content.type == "image"
169
+ assert content.mimeType == "image/png"
170
  # Verify base64 encoding
171
+ decoded = base64.b64decode(content.data)
172
  assert decoded == b"fake png data"
173
 
174
  async def test_tool_mixed_content(self):
 
177
  async with client_session(mcp._mcp_server) as client:
178
  result = await client.call_tool("mixed_content_tool_fn", {})
179
  assert len(result.content) == 2
180
+ content1 = result.content[0]
181
+ content2 = result.content[1]
182
+ assert isinstance(content1, TextContent)
183
+ assert content1.text == "Hello"
184
+ assert isinstance(content2, ImageContent)
185
+ assert content2.mimeType == "image/png"
186
+ assert content2.data == "abc"
187
 
188
  async def test_tool_mixed_list_with_image(self, tmp_path: Path):
189
  """Test that lists containing Image objects and other types are handled correctly"""
 
205
  result = await client.call_tool("mixed_list_fn", {})
206
  assert len(result.content) == 4
207
  # Check text conversion
208
+ content1 = result.content[0]
209
+ assert isinstance(content1, TextContent)
210
+ assert content1.text == "text message"
211
  # Check image conversion
212
+ content2 = result.content[1]
213
+ assert isinstance(content2, ImageContent)
214
+ assert content2.mimeType == "image/png"
215
+ assert base64.b64decode(content2.data) == b"test image data"
216
  # Check dict conversion
217
+ content3 = result.content[2]
218
+ assert isinstance(content3, TextContent)
219
+ assert '"key": "value"' in content3.text
220
  # Check direct TextContent
221
+ content4 = result.content[3]
222
+ assert isinstance(content4, TextContent)
223
+ assert content4.text == "direct content"
224
 
225
 
226
  class TestServerResources:
 
230
  def get_text():
231
  return "Hello, world!"
232
 
233
+ resource = FunctionResource(
234
+ uri=AnyUrl("resource://test"), name="test", fn=get_text
235
+ )
236
  mcp.add_resource(resource)
237
 
238
  async with client_session(mcp._mcp_server) as client:
239
+ result = await client.read_resource(AnyUrl("resource://test"))
240
+ assert isinstance(result.contents[0], TextResourceContents)
241
  assert result.contents[0].text == "Hello, world!"
242
 
243
  async def test_binary_resource(self):
 
247
  return b"Binary data"
248
 
249
  resource = FunctionResource(
250
+ uri=AnyUrl("resource://binary"),
251
  name="binary",
252
  fn=get_binary,
 
253
  mime_type="application/octet-stream",
254
  )
255
  mcp.add_resource(resource)
256
 
257
  async with client_session(mcp._mcp_server) as client:
258
+ result = await client.read_resource(AnyUrl("resource://binary"))
259
+ assert isinstance(result.contents[0], BlobResourceContents)
260
  assert result.contents[0].blob == base64.b64encode(b"Binary data").decode()
261
 
262
  async def test_file_resource_text(self, tmp_path: Path):
 
266
  text_file = tmp_path / "test.txt"
267
  text_file.write_text("Hello from file!")
268
 
269
+ resource = FileResource(
270
+ uri=AnyUrl("file://test.txt"), name="test.txt", path=text_file
271
+ )
272
  mcp.add_resource(resource)
273
 
274
  async with client_session(mcp._mcp_server) as client:
275
+ result = await client.read_resource(AnyUrl("file://test.txt"))
276
+ assert isinstance(result.contents[0], TextResourceContents)
277
  assert result.contents[0].text == "Hello from file!"
278
 
279
  async def test_file_resource_binary(self, tmp_path: Path):
 
284
  binary_file.write_bytes(b"Binary file data")
285
 
286
  resource = FileResource(
287
+ uri=AnyUrl("file://test.bin"),
288
  name="test.bin",
289
  path=binary_file,
 
290
  mime_type="application/octet-stream",
291
  )
292
  mcp.add_resource(resource)
293
 
294
  async with client_session(mcp._mcp_server) as client:
295
+ result = await client.read_resource(AnyUrl("file://test.bin"))
296
+ assert isinstance(result.contents[0], BlobResourceContents)
297
  assert (
298
  result.contents[0].blob
299
  == base64.b64encode(b"Binary file data").decode()
 
309
  with pytest.raises(ValueError, match="Mismatch between URI parameters"):
310
 
311
  @mcp.resource("resource://data")
312
+ def get_data_fn(param: str) -> str:
313
  return f"Data: {param}"
314
 
315
  async def test_resource_with_uri_params(self):
 
339
  return f"Data for {name}"
340
 
341
  async with client_session(mcp._mcp_server) as client:
342
+ result = await client.read_resource(AnyUrl("resource://test/data"))
343
+ assert isinstance(result.contents[0], TextResourceContents)
344
  assert result.contents[0].text == "Data for test"
345
 
346
  async def test_resource_mismatched_params(self):
 
362
  return f"Data for {org}/{repo}"
363
 
364
  async with client_session(mcp._mcp_server) as client:
365
+ result = await client.read_resource(
366
+ AnyUrl("resource://cursor/fastmcp/data")
367
+ )
368
+ assert isinstance(result.contents[0], TextResourceContents)
369
  assert result.contents[0].text == "Data for cursor/fastmcp"
370
 
371
  async def test_resource_multiple_mismatched_params(self):
 
375
  with pytest.raises(ValueError, match="Mismatch between URI parameters"):
376
 
377
  @mcp.resource("resource://{org}/{repo}/data")
378
+ def get_data_mismatched(org: str, repo_2: str) -> str:
379
  return f"Data for {org}"
380
 
381
  """Test that a resource with no parameters works as a regular resource"""
382
  mcp = FastMCP()
383
 
384
  @mcp.resource("resource://static")
385
+ def get_static_data() -> str:
386
  return "Static data"
387
 
388
  async with client_session(mcp._mcp_server) as client:
389
+ result = await client.read_resource(AnyUrl("resource://static"))
390
+ assert isinstance(result.contents[0], TextResourceContents)
391
  assert result.contents[0].text == "Static data"
392
 
393
  async def test_template_to_resource_conversion(self):
 
434
  async with client_session(mcp._mcp_server) as client:
435
  result = await client.call_tool("tool_with_context", {"x": 42})
436
  assert len(result.content) == 1
437
+ content = result.content[0]
438
+ assert isinstance(content, TextContent)
439
+ assert "Request" in content.text
440
+ assert "42" in content.text
441
 
442
  async def test_async_context(self):
443
  """Test that context works in async functions."""
 
451
  async with client_session(mcp._mcp_server) as client:
452
  result = await client.call_tool("async_tool", {"x": 42})
453
  assert len(result.content) == 1
454
+ content = result.content[0]
455
+ assert isinstance(content, TextContent)
456
+ assert "Async request" in content.text
457
+ assert "42" in content.text
458
 
459
  async def test_context_logging(self):
460
  """Test that context logging methods work."""
 
471
  async with client_session(mcp._mcp_server) as client:
472
  result = await client.call_tool("logging_tool", {"msg": "test"})
473
  assert len(result.content) == 1
474
+ content = result.content[0]
475
+ assert isinstance(content, TextContent)
476
+ assert "Logged messages for test" in content.text
477
 
478
  async def test_optional_context(self):
479
  """Test that context is optional."""
 
486
  async with client_session(mcp._mcp_server) as client:
487
  result = await client.call_tool("no_context", {"x": 21})
488
  assert len(result.content) == 1
489
+ content = result.content[0]
490
+ assert isinstance(content, TextContent)
491
+ assert content.text == "42"
492
 
493
  async def test_context_resource_access(self):
494
  """Test that context can access resources."""
 
506
  async with client_session(mcp._mcp_server) as client:
507
  result = await client.call_tool("tool_with_resource", {})
508
  assert len(result.content) == 1
509
+ content = result.content[0]
510
+ assert isinstance(content, TextContent)
511
+ assert "Read resource: resource data" in content.text
512
 
513
 
514
  class TestServerPrompts:
 
526
  assert len(prompts) == 1
527
  assert prompts[0].name == "fn"
528
  # Don't compare functions directly since validate_call wraps them
529
+ content = await prompts[0].render()
530
+ assert isinstance(content[0].content, TextContent)
531
+ assert content[0].content.text == "Hello, world!"
532
 
533
+ async def test_prompt_decorator_with_name(self):
534
  """Test prompt decorator with custom name."""
535
  mcp = FastMCP()
536
 
537
+ @mcp.prompt(name="custom_name")
538
  def fn() -> str:
539
  return "Hello, world!"
540
 
541
  prompts = mcp._prompt_manager.list_prompts()
542
  assert len(prompts) == 1
543
+ assert prompts[0].name == "custom_name"
544
+ content = await prompts[0].render()
545
+ assert isinstance(content[0].content, TextContent)
546
+ assert content[0].content.text == "Hello, world!"
547
 
548
+ async def test_prompt_decorator_with_description(self):
549
  """Test prompt decorator with custom description."""
550
  mcp = FastMCP()
551
 
 
556
  prompts = mcp._prompt_manager.list_prompts()
557
  assert len(prompts) == 1
558
  assert prompts[0].description == "A custom description"
559
+ content = await prompts[0].render()
560
+ assert isinstance(content[0].content, TextContent)
561
+ assert content[0].content.text == "Hello, world!"
562
 
563
  def test_prompt_decorator_error(self):
564
  """Test error when decorator is used incorrectly."""
565
  mcp = FastMCP()
566
  with pytest.raises(TypeError, match="decorator was used incorrectly"):
567
 
568
+ @mcp.prompt # type: ignore
569
  def fn() -> str:
570
  return "Hello, world!"
571
 
 
579
 
580
  async with client_session(mcp._mcp_server) as client:
581
  result = await client.list_prompts()
582
+ assert result.prompts is not None
583
  assert len(result.prompts) == 1
584
+ prompt = result.prompts[0]
585
+ assert prompt.name == "fn"
586
+ assert prompt.arguments is not None
587
+ assert len(prompt.arguments) == 2
588
+ assert prompt.arguments[0].name == "name"
589
+ assert prompt.arguments[0].required is True
590
+ assert prompt.arguments[1].name == "optional"
591
+ assert prompt.arguments[1].required is False
592
 
593
  async def test_get_prompt(self):
594
  """Test getting a prompt through MCP protocol."""
 
601
  async with client_session(mcp._mcp_server) as client:
602
  result = await client.get_prompt("fn", {"name": "World"})
603
  assert len(result.messages) == 1
604
+ message = result.messages[0]
605
+ assert message.role == "user"
606
+ content = message.content
607
+ assert isinstance(content, TextContent)
608
+ assert content.text == "Hello, World!"
609
 
610
  async def test_get_prompt_with_resource(self):
611
  """Test getting a prompt that returns resource content."""
 
616
  return UserMessage(
617
  content=EmbeddedResource(
618
  type="resource",
619
+ resource=TextResourceContents(
620
+ uri=AnyUrl("file://file.txt"),
621
+ text="File contents",
622
+ mimeType="text/plain",
623
+ ),
624
  )
625
  )
626
 
627
  async with client_session(mcp._mcp_server) as client:
628
  result = await client.get_prompt("fn")
629
  assert len(result.messages) == 1
630
+ message = result.messages[0]
631
+ assert message.role == "user"
632
+ content = message.content
633
+ assert isinstance(content, EmbeddedResource)
634
+ resource = content.resource
635
+ assert isinstance(resource, TextResourceContents)
636
+ assert resource.text == "File contents"
637
+ assert resource.mimeType == "text/plain"
638
 
639
  async def test_get_unknown_prompt(self):
640
  """Test error when getting unknown prompt."""
 
648
  mcp = FastMCP()
649
 
650
  @mcp.prompt()
651
+ def prompt_fn(name: str) -> str:
652
  return f"Hello, {name}!"
653
 
654
  async with client_session(mcp._mcp_server) as client:
655
  with pytest.raises(McpError, match="Missing required arguments"):
656
+ await client.get_prompt("prompt_fn")