@@ -437,3 +437,181 @@ async def search_items():
437
437
438
438
with pytest .raises (ValueError ):
439
439
FastApiMCP (app , include_tags = ["items" ], exclude_tags = ["write" ])
440
+
441
+
442
+ def test_filtering_edge_cases ():
443
+ """Test edge cases for the filtering functionality."""
444
+ app = FastAPI ()
445
+
446
+ # Define endpoints with different operation IDs and tags
447
+ @app .get ("/items/" , operation_id = "list_items" , tags = ["items" ])
448
+ async def list_items ():
449
+ return [{"id" : 1 }]
450
+
451
+ @app .get ("/items/{item_id}" , operation_id = "get_item" , tags = ["items" , "read" ])
452
+ async def get_item (item_id : int ):
453
+ return {"id" : item_id }
454
+
455
+ # Test with no filtering (default behavior)
456
+ default_mcp = FastApiMCP (app )
457
+ assert len (default_mcp .tools ) == 2
458
+ assert {tool .name for tool in default_mcp .tools } == {"get_item" , "list_items" }
459
+
460
+ # Test with empty include_operations
461
+ empty_include_ops_mcp = FastApiMCP (app , include_operations = [])
462
+ assert len (empty_include_ops_mcp .tools ) == 0
463
+ assert empty_include_ops_mcp .tools == []
464
+
465
+ # Test with empty exclude_operations (should include all)
466
+ empty_exclude_ops_mcp = FastApiMCP (app , exclude_operations = [])
467
+ assert len (empty_exclude_ops_mcp .tools ) == 2
468
+ assert {tool .name for tool in empty_exclude_ops_mcp .tools } == {"get_item" , "list_items" }
469
+
470
+ # Test with empty include_tags
471
+ empty_include_tags_mcp = FastApiMCP (app , include_tags = [])
472
+ assert len (empty_include_tags_mcp .tools ) == 0
473
+ assert empty_include_tags_mcp .tools == []
474
+
475
+ # Test with empty exclude_tags (should include all)
476
+ empty_exclude_tags_mcp = FastApiMCP (app , exclude_tags = [])
477
+ assert len (empty_exclude_tags_mcp .tools ) == 2
478
+ assert {tool .name for tool in empty_exclude_tags_mcp .tools } == {"get_item" , "list_items" }
479
+
480
+ # Test with non-existent operation IDs
481
+ nonexistent_ops_mcp = FastApiMCP (app , include_operations = ["non_existent_op" ])
482
+ assert len (nonexistent_ops_mcp .tools ) == 0
483
+ assert nonexistent_ops_mcp .tools == []
484
+
485
+ # Test with non-existent tags
486
+ nonexistent_tags_mcp = FastApiMCP (app , include_tags = ["non_existent_tag" ])
487
+ assert len (nonexistent_tags_mcp .tools ) == 0
488
+ assert nonexistent_tags_mcp .tools == []
489
+
490
+ # Test excluding non-existent operation IDs
491
+ exclude_nonexistent_ops_mcp = FastApiMCP (app , exclude_operations = ["non_existent_op" ])
492
+ assert len (exclude_nonexistent_ops_mcp .tools ) == 2
493
+ assert {tool .name for tool in exclude_nonexistent_ops_mcp .tools } == {"get_item" , "list_items" }
494
+
495
+ # Test excluding non-existent tags
496
+ exclude_nonexistent_tags_mcp = FastApiMCP (app , exclude_tags = ["non_existent_tag" ])
497
+ assert len (exclude_nonexistent_tags_mcp .tools ) == 2
498
+ assert {tool .name for tool in exclude_nonexistent_tags_mcp .tools } == {"get_item" , "list_items" }
499
+
500
+ # Test with an endpoint that has no tags
501
+ @app .get ("/no-tags" , operation_id = "no_tags" )
502
+ async def no_tags ():
503
+ return {"result" : "no tags" }
504
+
505
+ # Test include_tags with an endpoint that has no tags
506
+ no_tags_app_mcp = FastApiMCP (app , include_tags = ["items" ])
507
+ assert len (no_tags_app_mcp .tools ) == 2
508
+ assert "no_tags" not in {tool .name for tool in no_tags_app_mcp .tools }
509
+
510
+ # Test exclude_tags with an endpoint that has no tags
511
+ no_tags_exclude_mcp = FastApiMCP (app , exclude_tags = ["items" ])
512
+ assert len (no_tags_exclude_mcp .tools ) == 1
513
+ assert {tool .name for tool in no_tags_exclude_mcp .tools } == {"no_tags" }
514
+
515
+
516
+ def test_filtering_with_missing_operation_ids ():
517
+ """Test filtering behavior with endpoints that don't have operation IDs."""
518
+ app = FastAPI ()
519
+
520
+ # Define an endpoint with an operation ID
521
+ @app .get ("/items/" , operation_id = "list_items" , tags = ["items" ])
522
+ async def list_items ():
523
+ return [{"id" : 1 }]
524
+
525
+ # Define an endpoint without an operation ID
526
+ @app .get ("/no-op-id/" )
527
+ async def no_op_id ():
528
+ return {"result" : "no operation ID" }
529
+
530
+ # Test that both endpoints are discovered
531
+ default_mcp = FastApiMCP (app )
532
+
533
+ # FastAPI-MCP will generate an operation ID for endpoints without one
534
+ # The auto-generated ID will typically be 'no_op_id_no_op_id__get'
535
+ assert len (default_mcp .tools ) == 2
536
+
537
+ # Get the auto-generated operation ID
538
+ auto_generated_op_id = None
539
+ for tool in default_mcp .tools :
540
+ if tool .name != "list_items" :
541
+ auto_generated_op_id = tool .name
542
+ break
543
+
544
+ assert auto_generated_op_id is not None
545
+ assert "list_items" in {tool .name for tool in default_mcp .tools }
546
+
547
+ # Test include_operations with the known operation ID
548
+ include_ops_mcp = FastApiMCP (app , include_operations = ["list_items" ])
549
+ assert len (include_ops_mcp .tools ) == 1
550
+ assert {tool .name for tool in include_ops_mcp .tools } == {"list_items" }
551
+
552
+ # Test include_operations with the auto-generated operation ID
553
+ include_auto_ops_mcp = FastApiMCP (app , include_operations = [auto_generated_op_id ])
554
+ assert len (include_auto_ops_mcp .tools ) == 1
555
+ assert {tool .name for tool in include_auto_ops_mcp .tools } == {auto_generated_op_id }
556
+
557
+ # Test include_tags with a tag that matches the endpoint
558
+ include_tags_mcp = FastApiMCP (app , include_tags = ["items" ])
559
+ assert len (include_tags_mcp .tools ) == 1
560
+ assert {tool .name for tool in include_tags_mcp .tools } == {"list_items" }
561
+
562
+
563
+ def test_filter_with_empty_tools ():
564
+ """Test filtering with an empty tools list to ensure it handles this edge case correctly."""
565
+ # Create a FastAPI app without any routes
566
+ app = FastAPI ()
567
+
568
+ # Create MCP server (should have no tools)
569
+ empty_mcp = FastApiMCP (app )
570
+ assert len (empty_mcp .tools ) == 0
571
+
572
+ # Test filtering with various options on an empty app
573
+ include_ops_mcp = FastApiMCP (app , include_operations = ["some_op" ])
574
+ assert len (include_ops_mcp .tools ) == 0
575
+
576
+ exclude_ops_mcp = FastApiMCP (app , exclude_operations = ["some_op" ])
577
+ assert len (exclude_ops_mcp .tools ) == 0
578
+
579
+ include_tags_mcp = FastApiMCP (app , include_tags = ["some_tag" ])
580
+ assert len (include_tags_mcp .tools ) == 0
581
+
582
+ exclude_tags_mcp = FastApiMCP (app , exclude_tags = ["some_tag" ])
583
+ assert len (exclude_tags_mcp .tools ) == 0
584
+
585
+ # Test combined filtering
586
+ combined_mcp = FastApiMCP (app , include_operations = ["op" ], include_tags = ["tag" ])
587
+ assert len (combined_mcp .tools ) == 0
588
+
589
+
590
+ def test_filtering_with_empty_tags_array ():
591
+ """Test filtering behavior with endpoints that have empty tags array."""
592
+ app = FastAPI ()
593
+
594
+ # Define an endpoint with tags
595
+ @app .get ("/items/" , operation_id = "list_items" , tags = ["items" ])
596
+ async def list_items ():
597
+ return [{"id" : 1 }]
598
+
599
+ # Define an endpoint with an empty tags array
600
+ @app .get ("/empty-tags/" , operation_id = "empty_tags" , tags = [])
601
+ async def empty_tags ():
602
+ return {"result" : "empty tags" }
603
+
604
+ # Test default behavior
605
+ default_mcp = FastApiMCP (app )
606
+ assert len (default_mcp .tools ) == 2
607
+ assert {tool .name for tool in default_mcp .tools } == {"list_items" , "empty_tags" }
608
+
609
+ # Test include_tags
610
+ include_tags_mcp = FastApiMCP (app , include_tags = ["items" ])
611
+ assert len (include_tags_mcp .tools ) == 1
612
+ assert {tool .name for tool in include_tags_mcp .tools } == {"list_items" }
613
+
614
+ # Test exclude_tags
615
+ exclude_tags_mcp = FastApiMCP (app , exclude_tags = ["items" ])
616
+ assert len (exclude_tags_mcp .tools ) == 1
617
+ assert {tool .name for tool in exclude_tags_mcp .tools } == {"empty_tags" }
0 commit comments