@@ -437,3 +437,181 @@ async def search_items():
437437
438438 with pytest .raises (ValueError ):
439439 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