Jeremiah Lowin commited on
Commit
996e149
·
1 Parent(s): d14a99a

Update tests

Browse files
src/fastmcp/server/server.py CHANGED
@@ -1556,6 +1556,7 @@ class FastMCP(Generic[LifespanResultT]):
1556
  """
1557
  Create a FastMCP server from an OpenAPI specification.
1558
  """
 
1559
 
1560
  return FastMCPOpenAPI(
1561
  openapi_spec=openapi_spec,
 
1556
  """
1557
  Create a FastMCP server from an OpenAPI specification.
1558
  """
1559
+ from .openapi import FastMCPOpenAPI
1560
 
1561
  return FastMCPOpenAPI(
1562
  openapi_spec=openapi_spec,
tests/client/test_openapi.py CHANGED
@@ -6,6 +6,7 @@ from fastapi import FastAPI, Request
6
 
7
  from fastmcp import Client, FastMCP
8
  from fastmcp.client.transports import SSETransport, StreamableHttpTransport
 
9
  from fastmcp.utilities.tests import run_server_in_process
10
 
11
 
@@ -27,6 +28,16 @@ def fastmcp_server_for_headers() -> FastMCP:
27
  mcp = FastMCP.from_fastapi(
28
  app,
29
  httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
 
 
 
 
 
 
 
 
 
 
30
  )
31
 
32
  return mcp
 
6
 
7
  from fastmcp import Client, FastMCP
8
  from fastmcp.client.transports import SSETransport, StreamableHttpTransport
9
+ from fastmcp.server.openapi import MCPType, RouteMap
10
  from fastmcp.utilities.tests import run_server_in_process
11
 
12
 
 
28
  mcp = FastMCP.from_fastapi(
29
  app,
30
  httpx_client_kwargs={"headers": {"x-server-header": "test-abc"}},
31
+ route_maps=[
32
+ # GET requests with path parameters go to ResourceTemplate
33
+ RouteMap(
34
+ methods=["GET"],
35
+ pattern=r".*\{.*\}.*",
36
+ mcp_type=MCPType.RESOURCE_TEMPLATE,
37
+ ),
38
+ # GET requests without path parameters go to Resource
39
+ RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
40
+ ],
41
  )
42
 
43
  return mcp
tests/server/openapi/test_openapi.py CHANGED
@@ -46,6 +46,20 @@ def users_db() -> dict[int, User]:
46
  }
47
 
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  @pytest.fixture
50
  def fastapi_app(users_db: dict[int, User]) -> FastAPI:
51
  app = FastAPI(title="FastAPI App")
@@ -122,7 +136,7 @@ def api_client(fastapi_app: FastAPI) -> AsyncClient:
122
 
123
 
124
  @pytest.fixture
125
- async def fastmcp_openapi_server(
126
  fastapi_app: FastAPI, api_client: httpx.AsyncClient
127
  ) -> FastMCPOpenAPI:
128
  openapi_spec = fastapi_app.openapi()
@@ -131,6 +145,7 @@ async def fastmcp_openapi_server(
131
  openapi_spec=openapi_spec,
132
  client=api_client,
133
  name="Test App",
 
134
  )
135
 
136
 
@@ -169,6 +184,7 @@ async def test_create_openapi_server_with_timeout(
169
  client=api_client,
170
  name="Test App",
171
  timeout=1.0,
 
172
  )
173
  assert server._timeout == 1.0
174
 
@@ -186,11 +202,24 @@ async def test_create_openapi_server_with_timeout(
186
 
187
 
188
  class TestTools:
189
- async def test_list_tools(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  """
191
  By default, tools exclude GET methods
192
  """
193
- async with Client(fastmcp_openapi_server) as client:
194
  tools = await client.list_tools()
195
  assert len(tools) == 2
196
 
@@ -224,12 +253,14 @@ class TestTools:
224
  )
225
 
226
  async def test_call_create_user_tool(
227
- self, fastmcp_openapi_server: FastMCPOpenAPI, api_client
 
 
228
  ):
229
  """
230
  The tool created by the OpenAPI server should be the same as the original
231
  """
232
- async with Client(fastmcp_openapi_server) as client:
233
  tool_response = await client.call_tool(
234
  "create_user_users_post", {"name": "David", "active": False}
235
  )
@@ -243,19 +274,21 @@ class TestTools:
243
  assert len(response.json()) == 4
244
 
245
  # Check that the user was created via MCP
246
- async with Client(fastmcp_openapi_server) as client:
247
  user_response = await client.read_resource("resource://get_user_users/4")
248
  response_text = user_response[0].text # type: ignore[attr-defined]
249
  user = json.loads(response_text)
250
  assert user == expected_user
251
 
252
  async def test_call_update_user_name_tool(
253
- self, fastmcp_openapi_server: FastMCPOpenAPI, api_client
 
 
254
  ):
255
  """
256
  The tool created by the OpenAPI server should be the same as the original
257
  """
258
- async with Client(fastmcp_openapi_server) as client:
259
  tool_response = await client.call_tool(
260
  "update_user_name_users",
261
  {"user_id": 1, "name": "XYZ"},
@@ -270,7 +303,7 @@ class TestTools:
270
  assert expected_data in response.json()
271
 
272
  # Check that the user was updated via MCP
273
- async with Client(fastmcp_openapi_server) as client:
274
  user_response = await client.read_resource("resource://get_user_users/1")
275
  response_text = user_response[0].text # type: ignore[attr-defined]
276
  user = json.loads(response_text)
@@ -302,11 +335,13 @@ class TestTools:
302
 
303
 
304
  class TestResources:
305
- async def test_list_resources(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
 
306
  """
307
  By default, resources exclude GET methods without parameters
308
  """
309
- async with Client(fastmcp_openapi_server) as client:
310
  resources = await client.list_resources()
311
  assert len(resources) == 4
312
  assert resources[0].uri == AnyUrl("resource://get_users_users_get")
@@ -314,7 +349,7 @@ class TestResources:
314
 
315
  async def test_get_resource(
316
  self,
317
- fastmcp_openapi_server: FastMCPOpenAPI,
318
  api_client,
319
  users_db: dict[int, User],
320
  ):
@@ -325,7 +360,7 @@ class TestResources:
325
  json_users = TypeAdapter(list[User]).dump_python(
326
  sorted(users_db.values(), key=lambda x: x.id)
327
  )
328
- async with Client(fastmcp_openapi_server) as client:
329
  resource_response = await client.read_resource(
330
  "resource://get_users_users_get"
331
  )
@@ -337,11 +372,11 @@ class TestResources:
337
 
338
  async def test_get_bytes_resource(
339
  self,
340
- fastmcp_openapi_server: FastMCPOpenAPI,
341
  api_client,
342
  ):
343
  """Test reading a resource that returns bytes."""
344
- async with Client(fastmcp_openapi_server) as client:
345
  resource_response = await client.read_resource(
346
  "resource://ping_bytes_ping_bytes_get"
347
  )
@@ -350,23 +385,23 @@ class TestResources:
350
 
351
  async def test_get_str_resource(
352
  self,
353
- fastmcp_openapi_server: FastMCPOpenAPI,
354
  api_client,
355
  ):
356
  """Test reading a resource that returns a string."""
357
- async with Client(fastmcp_openapi_server) as client:
358
  resource_response = await client.read_resource("resource://ping_ping_get")
359
  assert resource_response[0].text == "pong" # type: ignore[attr-defined]
360
 
361
 
362
  class TestResourceTemplates:
363
  async def test_list_resource_templates(
364
- self, fastmcp_openapi_server: FastMCPOpenAPI
365
  ):
366
  """
367
  By default, resource templates exclude GET methods without parameters
368
  """
369
- async with Client(fastmcp_openapi_server) as client:
370
  resource_templates = await client.list_resource_templates()
371
  assert len(resource_templates) == 2
372
  assert resource_templates[0].name == "get_user_users"
@@ -381,7 +416,7 @@ class TestResourceTemplates:
381
 
382
  async def test_get_resource_template(
383
  self,
384
- fastmcp_openapi_server: FastMCPOpenAPI,
385
  api_client,
386
  users_db: dict[int, User],
387
  ):
@@ -389,7 +424,7 @@ class TestResourceTemplates:
389
  The resource template created by the OpenAPI server should be the same as the original
390
  """
391
  user_id = 2
392
- async with Client(fastmcp_openapi_server) as client:
393
  resource_response = await client.read_resource(
394
  f"resource://get_user_users/{user_id}"
395
  )
@@ -402,7 +437,7 @@ class TestResourceTemplates:
402
 
403
  async def test_get_resource_template_multi_param(
404
  self,
405
- fastmcp_openapi_server: FastMCPOpenAPI,
406
  api_client,
407
  users_db: dict[int, User],
408
  ):
@@ -411,7 +446,7 @@ class TestResourceTemplates:
411
  """
412
  user_id = 2
413
  is_active = True
414
- async with Client(fastmcp_openapi_server) as client:
415
  resource_response = await client.read_resource(
416
  f"resource://get_user_active_state_users/{is_active}/{user_id}"
417
  )
@@ -424,11 +459,13 @@ class TestResourceTemplates:
424
 
425
 
426
  class TestPrompts:
427
- async def test_list_prompts(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
 
428
  """
429
  By default, there are no prompts.
430
  """
431
- async with Client(fastmcp_openapi_server) as client:
432
  prompts = await client.list_prompts()
433
  assert len(prompts) == 0
434
 
@@ -437,11 +474,11 @@ class TestTagTransfer:
437
  """Tests for transferring tags from OpenAPI routes to MCP objects."""
438
 
439
  async def test_tags_transferred_to_tools(
440
- self, fastmcp_openapi_server: FastMCPOpenAPI
441
  ):
442
  """Test that tags from OpenAPI routes are correctly transferred to Tools."""
443
  # Get internal tools directly (not the public API which returns MCP.Content)
444
- tools = fastmcp_openapi_server._tool_manager.list_tools()
445
 
446
  # Find the create_user and update_user_name tools
447
  create_user_tool = next(
@@ -465,12 +502,12 @@ class TestTagTransfer:
465
  assert len(update_user_tool.tags) == 2
466
 
467
  async def test_tags_transferred_to_resources(
468
- self, fastmcp_openapi_server: FastMCPOpenAPI
469
  ):
470
  """Test that tags from OpenAPI routes are correctly transferred to Resources."""
471
  # Get internal resources directly
472
  resources = list(
473
- fastmcp_openapi_server._resource_manager.get_resources().values()
474
  )
475
 
476
  # Find the get_users resource
@@ -486,12 +523,12 @@ class TestTagTransfer:
486
  assert len(get_users_resource.tags) == 2
487
 
488
  async def test_tags_transferred_to_resource_templates(
489
- self, fastmcp_openapi_server: FastMCPOpenAPI
490
  ):
491
  """Test that tags from OpenAPI routes are correctly transferred to ResourceTemplates."""
492
  # Get internal resource templates directly
493
  templates = list(
494
- fastmcp_openapi_server._resource_manager.get_templates().values()
495
  )
496
 
497
  # Find the get_user template
@@ -507,12 +544,12 @@ class TestTagTransfer:
507
  assert len(get_user_template.tags) == 2
508
 
509
  async def test_tags_preserved_in_resources_created_from_templates(
510
- self, fastmcp_openapi_server: FastMCPOpenAPI
511
  ):
512
  """Test that tags are preserved when creating resources from templates."""
513
  # Get internal resource templates directly
514
  templates = list(
515
- fastmcp_openapi_server._resource_manager.get_templates().values()
516
  )
517
 
518
  # Find the get_user template
@@ -624,46 +661,49 @@ class TestOpenAPI30Compatibility:
624
  return httpx.AsyncClient(transport=transport, base_url="http://test")
625
 
626
  @pytest.fixture
627
- async def openapi_30_server(
628
  self, openapi_30_spec, mock_30_client
629
  ) -> FastMCPOpenAPI:
630
  """Create a FastMCPOpenAPI server from the OpenAPI 3.0 spec."""
631
  return FastMCPOpenAPI(
632
- openapi_spec=openapi_30_spec, client=mock_30_client, name="Product API 3.0"
 
 
 
633
  )
634
 
635
- async def test_server_creation(self, openapi_30_server):
636
  """Test that a server can be created from an OpenAPI 3.0 spec."""
637
- assert isinstance(openapi_30_server, FastMCP)
638
- assert openapi_30_server.name == "Product API 3.0"
639
 
640
- async def test_resource_discovery(self, openapi_30_server):
641
  """Test that resources are correctly discovered from an OpenAPI 3.0 spec."""
642
- async with Client(openapi_30_server) as client:
643
  resources = await client.list_resources()
644
  assert len(resources) == 1
645
  assert resources[0].uri == AnyUrl("resource://listProducts")
646
 
647
- async def test_resource_template_discovery(self, openapi_30_server):
648
  """Test that resource templates are correctly discovered from an OpenAPI 3.0 spec."""
649
- async with Client(openapi_30_server) as client:
650
  templates = await client.list_resource_templates()
651
  assert len(templates) == 1
652
  assert templates[0].name == "getProduct"
653
  assert templates[0].uriTemplate == r"resource://getProduct/{product_id}"
654
 
655
- async def test_tool_discovery(self, openapi_30_server):
656
  """Test that tools are correctly discovered from an OpenAPI 3.0 spec."""
657
- async with Client(openapi_30_server) as client:
658
  tools = await client.list_tools()
659
  assert len(tools) == 1
660
  assert tools[0].name == "createProduct"
661
  assert "name" in tools[0].inputSchema["properties"]
662
  assert "price" in tools[0].inputSchema["properties"]
663
 
664
- async def test_resource_access(self, openapi_30_server):
665
  """Test reading a resource from an OpenAPI 3.0 server."""
666
- async with Client(openapi_30_server) as client:
667
  resource_response = await client.read_resource("resource://listProducts")
668
  response_text = resource_response[0].text # type: ignore[attr-defined]
669
  content = json.loads(response_text)
@@ -671,9 +711,9 @@ class TestOpenAPI30Compatibility:
671
  assert content[0]["name"] == "Product 1"
672
  assert content[1]["name"] == "Product 2"
673
 
674
- async def test_resource_template_access(self, openapi_30_server):
675
  """Test reading a resource from template from an OpenAPI 3.0 server."""
676
- async with Client(openapi_30_server) as client:
677
  resource_response = await client.read_resource("resource://getProduct/p1")
678
  response_text = resource_response[0].text # type: ignore[attr-defined]
679
  content = json.loads(response_text)
@@ -681,9 +721,9 @@ class TestOpenAPI30Compatibility:
681
  assert content["name"] == "Product 1"
682
  assert content["price"] == 19.99
683
 
684
- async def test_tool_execution(self, openapi_30_server):
685
  """Test executing a tool from an OpenAPI 3.0 server."""
686
- async with Client(openapi_30_server) as client:
687
  result = await client.call_tool(
688
  "createProduct", {"name": "New Product", "price": 39.99}
689
  )
@@ -797,46 +837,49 @@ class TestOpenAPI31Compatibility:
797
  return httpx.AsyncClient(transport=transport, base_url="http://test")
798
 
799
  @pytest.fixture
800
- async def openapi_31_server(
801
  self, openapi_31_spec, mock_31_client
802
  ) -> FastMCPOpenAPI:
803
  """Create a FastMCPOpenAPI server from the OpenAPI 3.1 spec."""
804
  return FastMCPOpenAPI(
805
- openapi_spec=openapi_31_spec, client=mock_31_client, name="Order API 3.1"
 
 
 
806
  )
807
 
808
- async def test_server_creation(self, openapi_31_server):
809
  """Test that a server can be created from an OpenAPI 3.1 spec."""
810
- assert isinstance(openapi_31_server, FastMCP)
811
- assert openapi_31_server.name == "Order API 3.1"
812
 
813
- async def test_resource_discovery(self, openapi_31_server):
814
  """Test that resources are correctly discovered from an OpenAPI 3.1 spec."""
815
- async with Client(openapi_31_server) as client:
816
  resources = await client.list_resources()
817
  assert len(resources) == 1
818
  assert resources[0].uri == AnyUrl("resource://listOrders")
819
 
820
- async def test_resource_template_discovery(self, openapi_31_server):
821
  """Test that resource templates are correctly discovered from an OpenAPI 3.1 spec."""
822
- async with Client(openapi_31_server) as client:
823
  templates = await client.list_resource_templates()
824
  assert len(templates) == 1
825
  assert templates[0].name == "getOrder"
826
  assert templates[0].uriTemplate == r"resource://getOrder/{order_id}"
827
 
828
- async def test_tool_discovery(self, openapi_31_server):
829
  """Test that tools are correctly discovered from an OpenAPI 3.1 spec."""
830
- async with Client(openapi_31_server) as client:
831
  tools = await client.list_tools()
832
  assert len(tools) == 1
833
  assert tools[0].name == "createOrder"
834
  assert "customer" in tools[0].inputSchema["properties"]
835
  assert "items" in tools[0].inputSchema["properties"]
836
 
837
- async def test_resource_access(self, openapi_31_server):
838
  """Test reading a resource from an OpenAPI 3.1 server."""
839
- async with Client(openapi_31_server) as client:
840
  resource_response = await client.read_resource("resource://listOrders")
841
  response_text = resource_response[0].text # type: ignore[attr-defined]
842
  content = json.loads(response_text)
@@ -844,9 +887,9 @@ class TestOpenAPI31Compatibility:
844
  assert content[0]["customer"] == "Alice"
845
  assert content[1]["customer"] == "Bob"
846
 
847
- async def test_resource_template_access(self, openapi_31_server):
848
  """Test reading a resource from template from an OpenAPI 3.1 server."""
849
- async with Client(openapi_31_server) as client:
850
  resource_response = await client.read_resource("resource://getOrder/o1")
851
  response_text = resource_response[0].text # type: ignore[attr-defined]
852
  content = json.loads(response_text)
@@ -854,9 +897,9 @@ class TestOpenAPI31Compatibility:
854
  assert content["customer"] == "Alice"
855
  assert content["items"] == ["item1", "item2"]
856
 
857
- async def test_tool_execution(self, openapi_31_server):
858
  """Test executing a tool from an OpenAPI 3.1 server."""
859
- async with Client(openapi_31_server) as client:
860
  result = await client.call_tool(
861
  "createOrder", {"customer": "Charlie", "items": ["item4", "item5"]}
862
  )
@@ -871,11 +914,13 @@ class TestOpenAPI31Compatibility:
871
  class TestMountFastMCP:
872
  """Tests for mounting FastMCP servers."""
873
 
874
- async def test_mount_fastmcp(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
 
875
  """Test mounting an OpenAPI server."""
876
  mcp = FastMCP("MainApp")
877
 
878
- await mcp.import_server("fastapi", fastmcp_openapi_server)
879
 
880
  # Check that resources are available with prefixed URIs
881
  async with Client(mcp) as client:
@@ -1144,19 +1189,24 @@ class TestDescriptionPropagation:
1144
  return httpx.AsyncClient(transport=transport, base_url="http://test")
1145
 
1146
  @pytest.fixture
1147
- async def test_server(self, simple_openapi_spec, mock_client):
1148
  """Create a FastMCPOpenAPI server with the simple test spec."""
1149
  return FastMCPOpenAPI(
1150
  openapi_spec=simple_openapi_spec,
1151
  client=mock_client,
1152
  name="Test API",
 
1153
  )
1154
 
1155
  # --- RESOURCE TESTS ---
1156
 
1157
- async def test_resource_includes_route_description(self, test_server):
 
 
1158
  """Test that a Resource includes the route description."""
1159
- resources = list(test_server._resource_manager.get_resources().values())
 
 
1160
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1161
 
1162
  assert list_resource is not None, "listItems resource wasn't created"
@@ -1164,9 +1214,13 @@ class TestDescriptionPropagation:
1164
  "Route description missing from Resource"
1165
  )
1166
 
1167
- async def test_resource_includes_response_description(self, test_server):
 
 
1168
  """Test that a Resource includes the response description."""
1169
- resources = list(test_server._resource_manager.get_resources().values())
 
 
1170
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1171
 
1172
  assert list_resource is not None, "listItems resource wasn't created"
@@ -1174,9 +1228,13 @@ class TestDescriptionPropagation:
1174
  "Response description missing from Resource"
1175
  )
1176
 
1177
- async def test_resource_includes_response_model_fields(self, test_server):
 
 
1178
  """Test that a Resource description includes response model field descriptions."""
1179
- resources = list(test_server._resource_manager.get_resources().values())
 
 
1180
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1181
 
1182
  assert list_resource is not None, "listItems resource wasn't created"
@@ -1193,9 +1251,13 @@ class TestDescriptionPropagation:
1193
 
1194
  # --- RESOURCE TEMPLATE TESTS ---
1195
 
1196
- async def test_template_includes_route_description(self, test_server):
 
 
1197
  """Test that a ResourceTemplate includes the route description."""
1198
- templates = list(test_server._resource_manager.get_templates().values())
 
 
1199
  get_template = next((t for t in templates if t.name == "getItem"), None)
1200
 
1201
  assert get_template is not None, "getItem template wasn't created"
@@ -1203,9 +1265,13 @@ class TestDescriptionPropagation:
1203
  "Route description missing from ResourceTemplate"
1204
  )
1205
 
1206
- async def test_template_includes_function_docstring(self, test_server):
 
 
1207
  """Test that a ResourceTemplate includes the function docstring."""
1208
- templates = list(test_server._resource_manager.get_templates().values())
 
 
1209
  get_template = next((t for t in templates if t.name == "getItem"), None)
1210
 
1211
  assert get_template is not None, "getItem template wasn't created"
@@ -1213,9 +1279,13 @@ class TestDescriptionPropagation:
1213
  "Function docstring missing from ResourceTemplate"
1214
  )
1215
 
1216
- async def test_template_includes_path_parameter_description(self, test_server):
 
 
1217
  """Test that a ResourceTemplate includes path parameter descriptions."""
1218
- templates = list(test_server._resource_manager.get_templates().values())
 
 
1219
  get_template = next((t for t in templates if t.name == "getItem"), None)
1220
 
1221
  assert get_template is not None, "getItem template wasn't created"
@@ -1223,9 +1293,13 @@ class TestDescriptionPropagation:
1223
  "Path parameter description missing from ResourceTemplate description"
1224
  )
1225
 
1226
- async def test_template_includes_query_parameter_description(self, test_server):
 
 
1227
  """Test that a ResourceTemplate includes query parameter descriptions."""
1228
- templates = list(test_server._resource_manager.get_templates().values())
 
 
1229
  get_template = next((t for t in templates if t.name == "getItem"), None)
1230
 
1231
  assert get_template is not None, "getItem template wasn't created"
@@ -1233,9 +1307,13 @@ class TestDescriptionPropagation:
1233
  "Query parameter description missing from ResourceTemplate description"
1234
  )
1235
 
1236
- async def test_template_parameter_schema_includes_description(self, test_server):
 
 
1237
  """Test that a ResourceTemplate's parameter schema includes parameter descriptions."""
1238
- templates = list(test_server._resource_manager.get_templates().values())
 
 
1239
  get_template = next((t for t in templates if t.name == "getItem"), None)
1240
 
1241
  assert get_template is not None, "getItem template wasn't created"
@@ -1255,9 +1333,9 @@ class TestDescriptionPropagation:
1255
 
1256
  # --- TOOL TESTS ---
1257
 
1258
- async def test_tool_includes_route_description(self, test_server):
1259
  """Test that a Tool includes the route description."""
1260
- tools = test_server._tool_manager.list_tools()
1261
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1262
 
1263
  assert create_tool is not None, "createItem tool wasn't created"
@@ -1265,9 +1343,9 @@ class TestDescriptionPropagation:
1265
  "Route description missing from Tool"
1266
  )
1267
 
1268
- async def test_tool_includes_function_docstring(self, test_server):
1269
  """Test that a Tool includes the function docstring."""
1270
- tools = test_server._tool_manager.list_tools()
1271
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1272
 
1273
  assert create_tool is not None, "createItem tool wasn't created"
@@ -1277,10 +1355,10 @@ class TestDescriptionPropagation:
1277
  )
1278
 
1279
  async def test_tool_parameter_schema_includes_property_description(
1280
- self, test_server
1281
  ):
1282
  """Test that a Tool's parameter schema includes property descriptions from request model."""
1283
- tools = test_server._tool_manager.list_tools()
1284
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1285
 
1286
  assert create_tool is not None, "createItem tool wasn't created"
@@ -1300,9 +1378,9 @@ class TestDescriptionPropagation:
1300
 
1301
  # --- CLIENT API TESTS ---
1302
 
1303
- async def test_client_api_resource_description(self, test_server):
1304
  """Test that Resource descriptions are accessible via the client API."""
1305
- async with Client(test_server) as client:
1306
  resources = await client.list_resources()
1307
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1308
 
@@ -1314,9 +1392,9 @@ class TestDescriptionPropagation:
1314
  "Route description missing in Resource from client API"
1315
  )
1316
 
1317
- async def test_client_api_template_description(self, test_server):
1318
  """Test that ResourceTemplate descriptions are accessible via the client API."""
1319
- async with Client(test_server) as client:
1320
  templates = await client.list_resource_templates()
1321
  get_template = next((t for t in templates if t.name == "getItem"), None)
1322
 
@@ -1328,9 +1406,9 @@ class TestDescriptionPropagation:
1328
  "Route description missing in ResourceTemplate from client API"
1329
  )
1330
 
1331
- async def test_client_api_tool_description(self, test_server):
1332
  """Test that Tool descriptions are accessible via the client API."""
1333
- async with Client(test_server) as client:
1334
  tools = await client.list_tools()
1335
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1336
 
@@ -1342,9 +1420,9 @@ class TestDescriptionPropagation:
1342
  "Function docstring missing in Tool from client API"
1343
  )
1344
 
1345
- async def test_client_api_tool_parameter_schema(self, test_server):
1346
  """Test that Tool parameter schemas are accessible via the client API."""
1347
- async with Client(test_server) as client:
1348
  tools = await client.list_tools()
1349
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1350
 
@@ -1699,9 +1777,11 @@ class TestFastAPIDescriptionPropagation:
1699
  class TestReprMethods:
1700
  """Tests for the custom __repr__ methods of OpenAPI objects."""
1701
 
1702
- async def test_openapi_tool_repr(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
 
1703
  """Test that OpenAPITool's __repr__ method works without recursion errors."""
1704
- tools = fastmcp_openapi_server._tool_manager.list_tools()
1705
  tool = next(iter(tools))
1706
 
1707
  # Verify repr doesn't cause recursion and contains expected elements
@@ -1711,10 +1791,12 @@ class TestReprMethods:
1711
  assert "method=" in tool_repr
1712
  assert "path=" in tool_repr
1713
 
1714
- async def test_openapi_resource_repr(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
 
1715
  """Test that OpenAPIResource's __repr__ method works without recursion errors."""
1716
  resources = list(
1717
- fastmcp_openapi_server._resource_manager.get_resources().values()
1718
  )
1719
  resource = next(iter(resources))
1720
 
@@ -1726,11 +1808,11 @@ class TestReprMethods:
1726
  assert "path=" in resource_repr
1727
 
1728
  async def test_openapi_resource_template_repr(
1729
- self, fastmcp_openapi_server: FastMCPOpenAPI
1730
  ):
1731
  """Test that OpenAPIResourceTemplate's __repr__ method works without recursion errors."""
1732
  templates = list(
1733
- fastmcp_openapi_server._resource_manager.get_templates().values()
1734
  )
1735
  template = next(iter(templates))
1736
 
@@ -2182,6 +2264,7 @@ class TestMCPNames:
2182
  openapi_spec=mcp_names_openapi_spec,
2183
  client=mock_client,
2184
  mcp_names=mcp_names,
 
2185
  )
2186
 
2187
  # Check tools use custom names
@@ -2212,6 +2295,7 @@ class TestMCPNames:
2212
  openapi_spec=mcp_names_openapi_spec,
2213
  client=mock_client,
2214
  mcp_names=mcp_names,
 
2215
  )
2216
 
2217
  tools = server._tool_manager.list_tools()
@@ -2235,6 +2319,7 @@ class TestMCPNames:
2235
  server = FastMCPOpenAPI(
2236
  openapi_spec=mcp_names_openapi_spec,
2237
  client=mock_client,
 
2238
  )
2239
 
2240
  resources = list(server._resource_manager.get_resources().values())
@@ -2261,6 +2346,7 @@ class TestMCPNames:
2261
  server = FastMCPOpenAPI(
2262
  openapi_spec=mcp_names_openapi_spec,
2263
  client=mock_client,
 
2264
  )
2265
 
2266
  # Check all component types
@@ -2299,9 +2385,9 @@ class TestMCPNames:
2299
  mcp_names=mcp_names,
2300
  )
2301
 
2302
- resources = list(server._resource_manager.get_resources().values())
2303
- resource_names = {resource.name for resource in resources}
2304
- assert "openapi_user_list" in resource_names
2305
 
2306
  async def test_mcp_names_with_from_fastapi_classmethod(self):
2307
  """Test mcp_names works with FastMCP.from_fastapi() classmethod."""
@@ -2334,11 +2420,8 @@ class TestMCPNames:
2334
  tools = server._tool_manager.list_tools()
2335
  tool_names = {tool.name for tool in tools}
2336
 
2337
- resources = list(server._resource_manager.get_resources().values())
2338
- resource_names = {resource.name for resource in resources}
2339
-
2340
  assert "fastapi_create_user" in tool_names
2341
- assert "fastapi_user_list" in resource_names
2342
 
2343
  async def test_mcp_names_custom_names_are_also_truncated(
2344
  self, mcp_names_openapi_spec, mock_client
@@ -2355,6 +2438,7 @@ class TestMCPNames:
2355
  openapi_spec=mcp_names_openapi_spec,
2356
  client=mock_client,
2357
  mcp_names=mcp_names,
 
2358
  )
2359
 
2360
  resources = list(server._resource_manager.get_resources().values())
 
46
  }
47
 
48
 
49
+ # route maps for GET requests
50
+ # use these to create components of all types instead of just tools
51
+ GET_ROUTE_MAPS = [
52
+ # GET requests with path parameters go to ResourceTemplate
53
+ RouteMap(
54
+ methods=["GET"],
55
+ pattern=r".*\{.*\}.*",
56
+ mcp_type=MCPType.RESOURCE_TEMPLATE,
57
+ ),
58
+ # GET requests without path parameters go to Resource
59
+ RouteMap(methods=["GET"], pattern=r".*", mcp_type=MCPType.RESOURCE),
60
+ ]
61
+
62
+
63
  @pytest.fixture
64
  def fastapi_app(users_db: dict[int, User]) -> FastAPI:
65
  app = FastAPI(title="FastAPI App")
 
136
 
137
 
138
  @pytest.fixture
139
+ async def fastmcp_openapi_server_with_all_types(
140
  fastapi_app: FastAPI, api_client: httpx.AsyncClient
141
  ) -> FastMCPOpenAPI:
142
  openapi_spec = fastapi_app.openapi()
 
145
  openapi_spec=openapi_spec,
146
  client=api_client,
147
  name="Test App",
148
+ route_maps=GET_ROUTE_MAPS,
149
  )
150
 
151
 
 
184
  client=api_client,
185
  name="Test App",
186
  timeout=1.0,
187
+ route_maps=GET_ROUTE_MAPS,
188
  )
189
  assert server._timeout == 1.0
190
 
 
202
 
203
 
204
  class TestTools:
205
+ async def test_default_behavior_converts_everything_to_tools(
206
+ self, fastapi_app: FastAPI
207
+ ):
208
+ """
209
+ By default, tools exclude GET methods
210
+ """
211
+ server = FastMCPOpenAPI.from_fastapi(fastapi_app)
212
+ assert len(await server.get_tools()) == 8
213
+ assert len(await server.get_resources()) == 0
214
+ assert len(await server.get_resource_templates()) == 0
215
+
216
+ async def test_list_tools(
217
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
218
+ ):
219
  """
220
  By default, tools exclude GET methods
221
  """
222
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
223
  tools = await client.list_tools()
224
  assert len(tools) == 2
225
 
 
253
  )
254
 
255
  async def test_call_create_user_tool(
256
+ self,
257
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
258
+ api_client,
259
  ):
260
  """
261
  The tool created by the OpenAPI server should be the same as the original
262
  """
263
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
264
  tool_response = await client.call_tool(
265
  "create_user_users_post", {"name": "David", "active": False}
266
  )
 
274
  assert len(response.json()) == 4
275
 
276
  # Check that the user was created via MCP
277
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
278
  user_response = await client.read_resource("resource://get_user_users/4")
279
  response_text = user_response[0].text # type: ignore[attr-defined]
280
  user = json.loads(response_text)
281
  assert user == expected_user
282
 
283
  async def test_call_update_user_name_tool(
284
+ self,
285
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
286
+ api_client,
287
  ):
288
  """
289
  The tool created by the OpenAPI server should be the same as the original
290
  """
291
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
292
  tool_response = await client.call_tool(
293
  "update_user_name_users",
294
  {"user_id": 1, "name": "XYZ"},
 
303
  assert expected_data in response.json()
304
 
305
  # Check that the user was updated via MCP
306
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
307
  user_response = await client.read_resource("resource://get_user_users/1")
308
  response_text = user_response[0].text # type: ignore[attr-defined]
309
  user = json.loads(response_text)
 
335
 
336
 
337
  class TestResources:
338
+ async def test_list_resources(
339
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
340
+ ):
341
  """
342
  By default, resources exclude GET methods without parameters
343
  """
344
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
345
  resources = await client.list_resources()
346
  assert len(resources) == 4
347
  assert resources[0].uri == AnyUrl("resource://get_users_users_get")
 
349
 
350
  async def test_get_resource(
351
  self,
352
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
353
  api_client,
354
  users_db: dict[int, User],
355
  ):
 
360
  json_users = TypeAdapter(list[User]).dump_python(
361
  sorted(users_db.values(), key=lambda x: x.id)
362
  )
363
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
364
  resource_response = await client.read_resource(
365
  "resource://get_users_users_get"
366
  )
 
372
 
373
  async def test_get_bytes_resource(
374
  self,
375
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
376
  api_client,
377
  ):
378
  """Test reading a resource that returns bytes."""
379
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
380
  resource_response = await client.read_resource(
381
  "resource://ping_bytes_ping_bytes_get"
382
  )
 
385
 
386
  async def test_get_str_resource(
387
  self,
388
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
389
  api_client,
390
  ):
391
  """Test reading a resource that returns a string."""
392
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
393
  resource_response = await client.read_resource("resource://ping_ping_get")
394
  assert resource_response[0].text == "pong" # type: ignore[attr-defined]
395
 
396
 
397
  class TestResourceTemplates:
398
  async def test_list_resource_templates(
399
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
400
  ):
401
  """
402
  By default, resource templates exclude GET methods without parameters
403
  """
404
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
405
  resource_templates = await client.list_resource_templates()
406
  assert len(resource_templates) == 2
407
  assert resource_templates[0].name == "get_user_users"
 
416
 
417
  async def test_get_resource_template(
418
  self,
419
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
420
  api_client,
421
  users_db: dict[int, User],
422
  ):
 
424
  The resource template created by the OpenAPI server should be the same as the original
425
  """
426
  user_id = 2
427
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
428
  resource_response = await client.read_resource(
429
  f"resource://get_user_users/{user_id}"
430
  )
 
437
 
438
  async def test_get_resource_template_multi_param(
439
  self,
440
+ fastmcp_openapi_server_with_all_types: FastMCPOpenAPI,
441
  api_client,
442
  users_db: dict[int, User],
443
  ):
 
446
  """
447
  user_id = 2
448
  is_active = True
449
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
450
  resource_response = await client.read_resource(
451
  f"resource://get_user_active_state_users/{is_active}/{user_id}"
452
  )
 
459
 
460
 
461
  class TestPrompts:
462
+ async def test_list_prompts(
463
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
464
+ ):
465
  """
466
  By default, there are no prompts.
467
  """
468
+ async with Client(fastmcp_openapi_server_with_all_types) as client:
469
  prompts = await client.list_prompts()
470
  assert len(prompts) == 0
471
 
 
474
  """Tests for transferring tags from OpenAPI routes to MCP objects."""
475
 
476
  async def test_tags_transferred_to_tools(
477
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
478
  ):
479
  """Test that tags from OpenAPI routes are correctly transferred to Tools."""
480
  # Get internal tools directly (not the public API which returns MCP.Content)
481
+ tools = fastmcp_openapi_server_with_all_types._tool_manager.list_tools()
482
 
483
  # Find the create_user and update_user_name tools
484
  create_user_tool = next(
 
502
  assert len(update_user_tool.tags) == 2
503
 
504
  async def test_tags_transferred_to_resources(
505
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
506
  ):
507
  """Test that tags from OpenAPI routes are correctly transferred to Resources."""
508
  # Get internal resources directly
509
  resources = list(
510
+ fastmcp_openapi_server_with_all_types._resource_manager.get_resources().values()
511
  )
512
 
513
  # Find the get_users resource
 
523
  assert len(get_users_resource.tags) == 2
524
 
525
  async def test_tags_transferred_to_resource_templates(
526
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
527
  ):
528
  """Test that tags from OpenAPI routes are correctly transferred to ResourceTemplates."""
529
  # Get internal resource templates directly
530
  templates = list(
531
+ fastmcp_openapi_server_with_all_types._resource_manager.get_templates().values()
532
  )
533
 
534
  # Find the get_user template
 
544
  assert len(get_user_template.tags) == 2
545
 
546
  async def test_tags_preserved_in_resources_created_from_templates(
547
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
548
  ):
549
  """Test that tags are preserved when creating resources from templates."""
550
  # Get internal resource templates directly
551
  templates = list(
552
+ fastmcp_openapi_server_with_all_types._resource_manager.get_templates().values()
553
  )
554
 
555
  # Find the get_user template
 
661
  return httpx.AsyncClient(transport=transport, base_url="http://test")
662
 
663
  @pytest.fixture
664
+ async def openapi_30_server_with_all_types(
665
  self, openapi_30_spec, mock_30_client
666
  ) -> FastMCPOpenAPI:
667
  """Create a FastMCPOpenAPI server from the OpenAPI 3.0 spec."""
668
  return FastMCPOpenAPI(
669
+ openapi_spec=openapi_30_spec,
670
+ client=mock_30_client,
671
+ name="Product API 3.0",
672
+ route_maps=GET_ROUTE_MAPS,
673
  )
674
 
675
+ async def test_server_creation(self, openapi_30_server_with_all_types):
676
  """Test that a server can be created from an OpenAPI 3.0 spec."""
677
+ assert isinstance(openapi_30_server_with_all_types, FastMCP)
678
+ assert openapi_30_server_with_all_types.name == "Product API 3.0"
679
 
680
+ async def test_resource_discovery(self, openapi_30_server_with_all_types):
681
  """Test that resources are correctly discovered from an OpenAPI 3.0 spec."""
682
+ async with Client(openapi_30_server_with_all_types) as client:
683
  resources = await client.list_resources()
684
  assert len(resources) == 1
685
  assert resources[0].uri == AnyUrl("resource://listProducts")
686
 
687
+ async def test_resource_template_discovery(self, openapi_30_server_with_all_types):
688
  """Test that resource templates are correctly discovered from an OpenAPI 3.0 spec."""
689
+ async with Client(openapi_30_server_with_all_types) as client:
690
  templates = await client.list_resource_templates()
691
  assert len(templates) == 1
692
  assert templates[0].name == "getProduct"
693
  assert templates[0].uriTemplate == r"resource://getProduct/{product_id}"
694
 
695
+ async def test_tool_discovery(self, openapi_30_server_with_all_types):
696
  """Test that tools are correctly discovered from an OpenAPI 3.0 spec."""
697
+ async with Client(openapi_30_server_with_all_types) as client:
698
  tools = await client.list_tools()
699
  assert len(tools) == 1
700
  assert tools[0].name == "createProduct"
701
  assert "name" in tools[0].inputSchema["properties"]
702
  assert "price" in tools[0].inputSchema["properties"]
703
 
704
+ async def test_resource_access(self, openapi_30_server_with_all_types):
705
  """Test reading a resource from an OpenAPI 3.0 server."""
706
+ async with Client(openapi_30_server_with_all_types) as client:
707
  resource_response = await client.read_resource("resource://listProducts")
708
  response_text = resource_response[0].text # type: ignore[attr-defined]
709
  content = json.loads(response_text)
 
711
  assert content[0]["name"] == "Product 1"
712
  assert content[1]["name"] == "Product 2"
713
 
714
+ async def test_resource_template_access(self, openapi_30_server_with_all_types):
715
  """Test reading a resource from template from an OpenAPI 3.0 server."""
716
+ async with Client(openapi_30_server_with_all_types) as client:
717
  resource_response = await client.read_resource("resource://getProduct/p1")
718
  response_text = resource_response[0].text # type: ignore[attr-defined]
719
  content = json.loads(response_text)
 
721
  assert content["name"] == "Product 1"
722
  assert content["price"] == 19.99
723
 
724
+ async def test_tool_execution(self, openapi_30_server_with_all_types):
725
  """Test executing a tool from an OpenAPI 3.0 server."""
726
+ async with Client(openapi_30_server_with_all_types) as client:
727
  result = await client.call_tool(
728
  "createProduct", {"name": "New Product", "price": 39.99}
729
  )
 
837
  return httpx.AsyncClient(transport=transport, base_url="http://test")
838
 
839
  @pytest.fixture
840
+ async def openapi_31_server_with_all_types(
841
  self, openapi_31_spec, mock_31_client
842
  ) -> FastMCPOpenAPI:
843
  """Create a FastMCPOpenAPI server from the OpenAPI 3.1 spec."""
844
  return FastMCPOpenAPI(
845
+ openapi_spec=openapi_31_spec,
846
+ client=mock_31_client,
847
+ name="Order API 3.1",
848
+ route_maps=GET_ROUTE_MAPS,
849
  )
850
 
851
+ async def test_server_creation(self, openapi_31_server_with_all_types):
852
  """Test that a server can be created from an OpenAPI 3.1 spec."""
853
+ assert isinstance(openapi_31_server_with_all_types, FastMCP)
854
+ assert openapi_31_server_with_all_types.name == "Order API 3.1"
855
 
856
+ async def test_resource_discovery(self, openapi_31_server_with_all_types):
857
  """Test that resources are correctly discovered from an OpenAPI 3.1 spec."""
858
+ async with Client(openapi_31_server_with_all_types) as client:
859
  resources = await client.list_resources()
860
  assert len(resources) == 1
861
  assert resources[0].uri == AnyUrl("resource://listOrders")
862
 
863
+ async def test_resource_template_discovery(self, openapi_31_server_with_all_types):
864
  """Test that resource templates are correctly discovered from an OpenAPI 3.1 spec."""
865
+ async with Client(openapi_31_server_with_all_types) as client:
866
  templates = await client.list_resource_templates()
867
  assert len(templates) == 1
868
  assert templates[0].name == "getOrder"
869
  assert templates[0].uriTemplate == r"resource://getOrder/{order_id}"
870
 
871
+ async def test_tool_discovery(self, openapi_31_server_with_all_types):
872
  """Test that tools are correctly discovered from an OpenAPI 3.1 spec."""
873
+ async with Client(openapi_31_server_with_all_types) as client:
874
  tools = await client.list_tools()
875
  assert len(tools) == 1
876
  assert tools[0].name == "createOrder"
877
  assert "customer" in tools[0].inputSchema["properties"]
878
  assert "items" in tools[0].inputSchema["properties"]
879
 
880
+ async def test_resource_access(self, openapi_31_server_with_all_types):
881
  """Test reading a resource from an OpenAPI 3.1 server."""
882
+ async with Client(openapi_31_server_with_all_types) as client:
883
  resource_response = await client.read_resource("resource://listOrders")
884
  response_text = resource_response[0].text # type: ignore[attr-defined]
885
  content = json.loads(response_text)
 
887
  assert content[0]["customer"] == "Alice"
888
  assert content[1]["customer"] == "Bob"
889
 
890
+ async def test_resource_template_access(self, openapi_31_server_with_all_types):
891
  """Test reading a resource from template from an OpenAPI 3.1 server."""
892
+ async with Client(openapi_31_server_with_all_types) as client:
893
  resource_response = await client.read_resource("resource://getOrder/o1")
894
  response_text = resource_response[0].text # type: ignore[attr-defined]
895
  content = json.loads(response_text)
 
897
  assert content["customer"] == "Alice"
898
  assert content["items"] == ["item1", "item2"]
899
 
900
+ async def test_tool_execution(self, openapi_31_server_with_all_types):
901
  """Test executing a tool from an OpenAPI 3.1 server."""
902
+ async with Client(openapi_31_server_with_all_types) as client:
903
  result = await client.call_tool(
904
  "createOrder", {"customer": "Charlie", "items": ["item4", "item5"]}
905
  )
 
914
  class TestMountFastMCP:
915
  """Tests for mounting FastMCP servers."""
916
 
917
+ async def test_mount_fastmcp(
918
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
919
+ ):
920
  """Test mounting an OpenAPI server."""
921
  mcp = FastMCP("MainApp")
922
 
923
+ await mcp.import_server("fastapi", fastmcp_openapi_server_with_all_types)
924
 
925
  # Check that resources are available with prefixed URIs
926
  async with Client(mcp) as client:
 
1189
  return httpx.AsyncClient(transport=transport, base_url="http://test")
1190
 
1191
  @pytest.fixture
1192
+ async def simple_server_with_all_types(self, simple_openapi_spec, mock_client):
1193
  """Create a FastMCPOpenAPI server with the simple test spec."""
1194
  return FastMCPOpenAPI(
1195
  openapi_spec=simple_openapi_spec,
1196
  client=mock_client,
1197
  name="Test API",
1198
+ route_maps=GET_ROUTE_MAPS,
1199
  )
1200
 
1201
  # --- RESOURCE TESTS ---
1202
 
1203
+ async def test_resource_includes_route_description(
1204
+ self, simple_server_with_all_types
1205
+ ):
1206
  """Test that a Resource includes the route description."""
1207
+ resources = list(
1208
+ simple_server_with_all_types._resource_manager.get_resources().values()
1209
+ )
1210
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1211
 
1212
  assert list_resource is not None, "listItems resource wasn't created"
 
1214
  "Route description missing from Resource"
1215
  )
1216
 
1217
+ async def test_resource_includes_response_description(
1218
+ self, simple_server_with_all_types
1219
+ ):
1220
  """Test that a Resource includes the response description."""
1221
+ resources = list(
1222
+ simple_server_with_all_types._resource_manager.get_resources().values()
1223
+ )
1224
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1225
 
1226
  assert list_resource is not None, "listItems resource wasn't created"
 
1228
  "Response description missing from Resource"
1229
  )
1230
 
1231
+ async def test_resource_includes_response_model_fields(
1232
+ self, simple_server_with_all_types
1233
+ ):
1234
  """Test that a Resource description includes response model field descriptions."""
1235
+ resources = list(
1236
+ simple_server_with_all_types._resource_manager.get_resources().values()
1237
+ )
1238
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1239
 
1240
  assert list_resource is not None, "listItems resource wasn't created"
 
1251
 
1252
  # --- RESOURCE TEMPLATE TESTS ---
1253
 
1254
+ async def test_template_includes_route_description(
1255
+ self, simple_server_with_all_types
1256
+ ):
1257
  """Test that a ResourceTemplate includes the route description."""
1258
+ templates = list(
1259
+ simple_server_with_all_types._resource_manager.get_templates().values()
1260
+ )
1261
  get_template = next((t for t in templates if t.name == "getItem"), None)
1262
 
1263
  assert get_template is not None, "getItem template wasn't created"
 
1265
  "Route description missing from ResourceTemplate"
1266
  )
1267
 
1268
+ async def test_template_includes_function_docstring(
1269
+ self, simple_server_with_all_types
1270
+ ):
1271
  """Test that a ResourceTemplate includes the function docstring."""
1272
+ templates = list(
1273
+ simple_server_with_all_types._resource_manager.get_templates().values()
1274
+ )
1275
  get_template = next((t for t in templates if t.name == "getItem"), None)
1276
 
1277
  assert get_template is not None, "getItem template wasn't created"
 
1279
  "Function docstring missing from ResourceTemplate"
1280
  )
1281
 
1282
+ async def test_template_includes_path_parameter_description(
1283
+ self, simple_server_with_all_types
1284
+ ):
1285
  """Test that a ResourceTemplate includes path parameter descriptions."""
1286
+ templates = list(
1287
+ simple_server_with_all_types._resource_manager.get_templates().values()
1288
+ )
1289
  get_template = next((t for t in templates if t.name == "getItem"), None)
1290
 
1291
  assert get_template is not None, "getItem template wasn't created"
 
1293
  "Path parameter description missing from ResourceTemplate description"
1294
  )
1295
 
1296
+ async def test_template_includes_query_parameter_description(
1297
+ self, simple_server_with_all_types
1298
+ ):
1299
  """Test that a ResourceTemplate includes query parameter descriptions."""
1300
+ templates = list(
1301
+ simple_server_with_all_types._resource_manager.get_templates().values()
1302
+ )
1303
  get_template = next((t for t in templates if t.name == "getItem"), None)
1304
 
1305
  assert get_template is not None, "getItem template wasn't created"
 
1307
  "Query parameter description missing from ResourceTemplate description"
1308
  )
1309
 
1310
+ async def test_template_parameter_schema_includes_description(
1311
+ self, simple_server_with_all_types
1312
+ ):
1313
  """Test that a ResourceTemplate's parameter schema includes parameter descriptions."""
1314
+ templates = list(
1315
+ simple_server_with_all_types._resource_manager.get_templates().values()
1316
+ )
1317
  get_template = next((t for t in templates if t.name == "getItem"), None)
1318
 
1319
  assert get_template is not None, "getItem template wasn't created"
 
1333
 
1334
  # --- TOOL TESTS ---
1335
 
1336
+ async def test_tool_includes_route_description(self, simple_server_with_all_types):
1337
  """Test that a Tool includes the route description."""
1338
+ tools = simple_server_with_all_types._tool_manager.list_tools()
1339
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1340
 
1341
  assert create_tool is not None, "createItem tool wasn't created"
 
1343
  "Route description missing from Tool"
1344
  )
1345
 
1346
+ async def test_tool_includes_function_docstring(self, simple_server_with_all_types):
1347
  """Test that a Tool includes the function docstring."""
1348
+ tools = simple_server_with_all_types._tool_manager.list_tools()
1349
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1350
 
1351
  assert create_tool is not None, "createItem tool wasn't created"
 
1355
  )
1356
 
1357
  async def test_tool_parameter_schema_includes_property_description(
1358
+ self, simple_server_with_all_types
1359
  ):
1360
  """Test that a Tool's parameter schema includes property descriptions from request model."""
1361
+ tools = simple_server_with_all_types._tool_manager.list_tools()
1362
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1363
 
1364
  assert create_tool is not None, "createItem tool wasn't created"
 
1378
 
1379
  # --- CLIENT API TESTS ---
1380
 
1381
+ async def test_client_api_resource_description(self, simple_server_with_all_types):
1382
  """Test that Resource descriptions are accessible via the client API."""
1383
+ async with Client(simple_server_with_all_types) as client:
1384
  resources = await client.list_resources()
1385
  list_resource = next((r for r in resources if r.name == "listItems"), None)
1386
 
 
1392
  "Route description missing in Resource from client API"
1393
  )
1394
 
1395
+ async def test_client_api_template_description(self, simple_server_with_all_types):
1396
  """Test that ResourceTemplate descriptions are accessible via the client API."""
1397
+ async with Client(simple_server_with_all_types) as client:
1398
  templates = await client.list_resource_templates()
1399
  get_template = next((t for t in templates if t.name == "getItem"), None)
1400
 
 
1406
  "Route description missing in ResourceTemplate from client API"
1407
  )
1408
 
1409
+ async def test_client_api_tool_description(self, simple_server_with_all_types):
1410
  """Test that Tool descriptions are accessible via the client API."""
1411
+ async with Client(simple_server_with_all_types) as client:
1412
  tools = await client.list_tools()
1413
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1414
 
 
1420
  "Function docstring missing in Tool from client API"
1421
  )
1422
 
1423
+ async def test_client_api_tool_parameter_schema(self, simple_server_with_all_types):
1424
  """Test that Tool parameter schemas are accessible via the client API."""
1425
+ async with Client(simple_server_with_all_types) as client:
1426
  tools = await client.list_tools()
1427
  create_tool = next((t for t in tools if t.name == "createItem"), None)
1428
 
 
1777
  class TestReprMethods:
1778
  """Tests for the custom __repr__ methods of OpenAPI objects."""
1779
 
1780
+ async def test_openapi_tool_repr(
1781
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
1782
+ ):
1783
  """Test that OpenAPITool's __repr__ method works without recursion errors."""
1784
+ tools = fastmcp_openapi_server_with_all_types._tool_manager.list_tools()
1785
  tool = next(iter(tools))
1786
 
1787
  # Verify repr doesn't cause recursion and contains expected elements
 
1791
  assert "method=" in tool_repr
1792
  assert "path=" in tool_repr
1793
 
1794
+ async def test_openapi_resource_repr(
1795
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
1796
+ ):
1797
  """Test that OpenAPIResource's __repr__ method works without recursion errors."""
1798
  resources = list(
1799
+ fastmcp_openapi_server_with_all_types._resource_manager.get_resources().values()
1800
  )
1801
  resource = next(iter(resources))
1802
 
 
1808
  assert "path=" in resource_repr
1809
 
1810
  async def test_openapi_resource_template_repr(
1811
+ self, fastmcp_openapi_server_with_all_types: FastMCPOpenAPI
1812
  ):
1813
  """Test that OpenAPIResourceTemplate's __repr__ method works without recursion errors."""
1814
  templates = list(
1815
+ fastmcp_openapi_server_with_all_types._resource_manager.get_templates().values()
1816
  )
1817
  template = next(iter(templates))
1818
 
 
2264
  openapi_spec=mcp_names_openapi_spec,
2265
  client=mock_client,
2266
  mcp_names=mcp_names,
2267
+ route_maps=GET_ROUTE_MAPS,
2268
  )
2269
 
2270
  # Check tools use custom names
 
2295
  openapi_spec=mcp_names_openapi_spec,
2296
  client=mock_client,
2297
  mcp_names=mcp_names,
2298
+ route_maps=GET_ROUTE_MAPS,
2299
  )
2300
 
2301
  tools = server._tool_manager.list_tools()
 
2319
  server = FastMCPOpenAPI(
2320
  openapi_spec=mcp_names_openapi_spec,
2321
  client=mock_client,
2322
+ route_maps=GET_ROUTE_MAPS,
2323
  )
2324
 
2325
  resources = list(server._resource_manager.get_resources().values())
 
2346
  server = FastMCPOpenAPI(
2347
  openapi_spec=mcp_names_openapi_spec,
2348
  client=mock_client,
2349
+ route_maps=GET_ROUTE_MAPS,
2350
  )
2351
 
2352
  # Check all component types
 
2385
  mcp_names=mcp_names,
2386
  )
2387
 
2388
+ tools = server._tool_manager.list_tools()
2389
+ tool_names = {tool.name for tool in tools}
2390
+ assert "openapi_user_list" in tool_names
2391
 
2392
  async def test_mcp_names_with_from_fastapi_classmethod(self):
2393
  """Test mcp_names works with FastMCP.from_fastapi() classmethod."""
 
2420
  tools = server._tool_manager.list_tools()
2421
  tool_names = {tool.name for tool in tools}
2422
 
 
 
 
2423
  assert "fastapi_create_user" in tool_names
2424
+ assert "fastapi_user_list" in tool_names
2425
 
2426
  async def test_mcp_names_custom_names_are_also_truncated(
2427
  self, mcp_names_openapi_spec, mock_client
 
2438
  openapi_spec=mcp_names_openapi_spec,
2439
  client=mock_client,
2440
  mcp_names=mcp_names,
2441
+ route_maps=GET_ROUTE_MAPS,
2442
  )
2443
 
2444
  resources = list(server._resource_manager.get_resources().values())
tests/server/openapi/test_route_map_fn.py CHANGED
@@ -161,15 +161,15 @@ def test_route_map_fn_returns_none(sample_openapi_spec, http_client):
161
 
162
  # Should have default behavior
163
  assert server.name == "Test Server"
164
- # Check that components were created with default types
165
  tools = server._tool_manager._tools
166
  resources = server._resource_manager._resources
167
  templates = server._resource_manager._templates
168
 
169
  # Should have tools, resources, and templates based on default mapping
170
  assert len(tools) > 0
171
- assert len(resources) > 0
172
- assert len(templates) > 0
173
 
174
 
175
  def test_route_map_fn_called_for_excluded_routes(sample_openapi_spec, http_client):
 
161
 
162
  # Should have default behavior
163
  assert server.name == "Test Server"
164
+ # Check that components were created with default mapping
165
  tools = server._tool_manager._tools
166
  resources = server._resource_manager._resources
167
  templates = server._resource_manager._templates
168
 
169
  # Should have tools, resources, and templates based on default mapping
170
  assert len(tools) > 0
171
+ assert len(resources) == 0
172
+ assert len(templates) == 0
173
 
174
 
175
  def test_route_map_fn_called_for_excluded_routes(sample_openapi_spec, http_client):