Skip to content

Commit 82d748b

Browse files
committed
feat: add ignore_deprecated parameter to filter deprecated operations
1 parent 7ef4373 commit 82d748b

File tree

4 files changed

+490
-1
lines changed

4 files changed

+490
-1
lines changed

fastapi_mcp/openapi/convert.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def convert_openapi_to_mcp_tools(
1818
openapi_schema: Dict[str, Any],
1919
describe_all_responses: bool = False,
2020
describe_full_response_schema: bool = False,
21+
ignore_deprecated: bool = True,
2122
) -> Tuple[List[types.Tool], Dict[str, Dict[str, Any]]]:
2223
"""
2324
Convert OpenAPI operations to MCP tools.
@@ -26,6 +27,7 @@ def convert_openapi_to_mcp_tools(
2627
openapi_schema: The OpenAPI schema
2728
describe_all_responses: Whether to include all possible response schemas in tool descriptions
2829
describe_full_response_schema: Whether to include full response schema in tool descriptions
30+
ignore_deprecated: Whether to ignore deprecated operations when converting to MCP tools
2931
3032
Returns:
3133
A tuple containing:
@@ -47,7 +49,7 @@ def convert_openapi_to_mcp_tools(
4749
continue
4850

4951
is_deprecated = operation.get("deprecated", False)
50-
if is_deprecated:
52+
if is_deprecated and ignore_deprecated:
5153
logger.warning(f"Skipping deprecated operation: {method} {path}")
5254
continue
5355

fastapi_mcp/server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ def __init__(
8484
"""
8585
),
8686
] = ["authorization"],
87+
ignore_deprecated: Annotated[
88+
bool,
89+
Doc("Whether to ignore deprecated operations when converting OpenAPI to MCP tools. Defaults to True."),
90+
] = True,
8791
):
8892
# Validate operation and tag filtering options
8993
if include_operations is not None and exclude_operations is not None:
@@ -112,6 +116,8 @@ def __init__(
112116
if self._auth_config:
113117
self._auth_config = self._auth_config.model_validate(self._auth_config)
114118

119+
self._ignore_deprecated = ignore_deprecated
120+
115121
self._http_client = http_client or httpx.AsyncClient(
116122
transport=httpx.ASGITransport(app=self.fastapi, raise_app_exceptions=False),
117123
base_url=self._base_url,
@@ -136,6 +142,7 @@ def setup_server(self) -> None:
136142
openapi_schema,
137143
describe_all_responses=self._describe_all_responses,
138144
describe_full_response_schema=self._describe_full_response_schema,
145+
ignore_deprecated=self._ignore_deprecated,
139146
)
140147

141148
# Filter tools based on operation IDs and tags

tests/test_configuration.py

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,254 @@ async def empty_tags():
581581
exclude_tags_mcp = FastApiMCP(app, exclude_tags=["items"])
582582
assert len(exclude_tags_mcp.tools) == 1
583583
assert {tool.name for tool in exclude_tags_mcp.tools} == {"empty_tags"}
584+
585+
586+
def test_ignore_deprecated_default_behavior(simple_fastapi_app: FastAPI):
587+
"""Test that deprecated operations are ignored by default in FastApiMCP."""
588+
589+
# Add deprecated operations to the simple app
590+
@simple_fastapi_app.get(
591+
"/deprecated/items/",
592+
response_model=list,
593+
tags=["deprecated"],
594+
operation_id="list_deprecated_items",
595+
deprecated=True,
596+
)
597+
async def list_deprecated_items():
598+
"""[DEPRECATED] List all items (deprecated version)."""
599+
return []
600+
601+
@simple_fastapi_app.get(
602+
"/deprecated/items/{item_id}",
603+
response_model=dict,
604+
tags=["deprecated"],
605+
operation_id="get_deprecated_item",
606+
deprecated=True,
607+
)
608+
async def get_deprecated_item(item_id: int):
609+
"""[DEPRECATED] Get a specific item by its ID (deprecated version)."""
610+
return {"id": item_id}
611+
612+
@simple_fastapi_app.post(
613+
"/deprecated/items/",
614+
response_model=dict,
615+
tags=["deprecated"],
616+
operation_id="create_deprecated_item",
617+
deprecated=True,
618+
)
619+
async def create_deprecated_item():
620+
"""[DEPRECATED] Create a new item in the database (deprecated version)."""
621+
return {"id": 1}
622+
623+
mcp_server = FastApiMCP(simple_fastapi_app)
624+
625+
# Should include regular operations but exclude deprecated ones
626+
expected_operations = ["list_items", "get_item", "create_item", "update_item", "delete_item", "raise_error"]
627+
deprecated_operations = ["list_deprecated_items", "get_deprecated_item", "create_deprecated_item"]
628+
629+
assert len(mcp_server.tools) == len(expected_operations)
630+
assert len(mcp_server.operation_map) == len(expected_operations)
631+
632+
for op in expected_operations:
633+
assert op in mcp_server.operation_map
634+
635+
for op in deprecated_operations:
636+
assert op not in mcp_server.operation_map
637+
638+
for tool in mcp_server.tools:
639+
assert tool.name in expected_operations
640+
assert tool.name not in deprecated_operations
641+
642+
643+
def test_ignore_deprecated_false(simple_fastapi_app: FastAPI):
644+
"""Test that deprecated operations are included when ignore_deprecated=False."""
645+
646+
# Add deprecated operations to the simple app
647+
@simple_fastapi_app.get(
648+
"/deprecated/items/",
649+
response_model=list,
650+
tags=["deprecated"],
651+
operation_id="list_deprecated_items",
652+
deprecated=True,
653+
)
654+
async def list_deprecated_items():
655+
"""[DEPRECATED] List all items (deprecated version)."""
656+
return []
657+
658+
@simple_fastapi_app.get(
659+
"/deprecated/items/{item_id}",
660+
response_model=dict,
661+
tags=["deprecated"],
662+
operation_id="get_deprecated_item",
663+
deprecated=True,
664+
)
665+
async def get_deprecated_item(item_id: int):
666+
"""[DEPRECATED] Get a specific item by its ID (deprecated version)."""
667+
return {"id": item_id}
668+
669+
@simple_fastapi_app.post(
670+
"/deprecated/items/",
671+
response_model=dict,
672+
tags=["deprecated"],
673+
operation_id="create_deprecated_item",
674+
deprecated=True,
675+
)
676+
async def create_deprecated_item():
677+
"""[DEPRECATED] Create a new item in the database (deprecated version)."""
678+
return {"id": 1}
679+
680+
mcp_server = FastApiMCP(simple_fastapi_app, ignore_deprecated=False)
681+
682+
# Should include both regular and deprecated operations
683+
expected_operations = ["list_items", "get_item", "create_item", "update_item", "delete_item", "raise_error"]
684+
deprecated_operations = ["list_deprecated_items", "get_deprecated_item", "create_deprecated_item"]
685+
all_operations = expected_operations + deprecated_operations
686+
687+
assert len(mcp_server.tools) == len(all_operations)
688+
assert len(mcp_server.operation_map) == len(all_operations)
689+
690+
for op in all_operations:
691+
assert op in mcp_server.operation_map
692+
693+
for tool in mcp_server.tools:
694+
assert tool.name in all_operations
695+
696+
697+
def test_ignore_deprecated_true_explicit(simple_fastapi_app: FastAPI):
698+
"""Test that deprecated operations are excluded when ignore_deprecated=True explicitly."""
699+
700+
# Add deprecated operations to the simple app
701+
@simple_fastapi_app.get(
702+
"/deprecated/items/",
703+
response_model=list,
704+
tags=["deprecated"],
705+
operation_id="list_deprecated_items",
706+
deprecated=True,
707+
)
708+
async def list_deprecated_items():
709+
"""[DEPRECATED] List all items (deprecated version)."""
710+
return []
711+
712+
@simple_fastapi_app.get(
713+
"/deprecated/items/{item_id}",
714+
response_model=dict,
715+
tags=["deprecated"],
716+
operation_id="get_deprecated_item",
717+
deprecated=True,
718+
)
719+
async def get_deprecated_item(item_id: int):
720+
"""[DEPRECATED] Get a specific item by its ID (deprecated version)."""
721+
return {"id": item_id}
722+
723+
@simple_fastapi_app.post(
724+
"/deprecated/items/",
725+
response_model=dict,
726+
tags=["deprecated"],
727+
operation_id="create_deprecated_item",
728+
deprecated=True,
729+
)
730+
async def create_deprecated_item():
731+
"""[DEPRECATED] Create a new item in the database (deprecated version)."""
732+
return {"id": 1}
733+
734+
mcp_server = FastApiMCP(simple_fastapi_app, ignore_deprecated=True)
735+
736+
# Should include regular operations but exclude deprecated ones
737+
expected_operations = ["list_items", "get_item", "create_item", "update_item", "delete_item", "raise_error"]
738+
deprecated_operations = ["list_deprecated_items", "get_deprecated_item", "create_deprecated_item"]
739+
740+
assert len(mcp_server.tools) == len(expected_operations)
741+
assert len(mcp_server.operation_map) == len(expected_operations)
742+
743+
for op in expected_operations:
744+
assert op in mcp_server.operation_map
745+
746+
for op in deprecated_operations:
747+
assert op not in mcp_server.operation_map
748+
749+
for tool in mcp_server.tools:
750+
assert tool.name in expected_operations
751+
assert tool.name not in deprecated_operations
752+
753+
754+
def test_ignore_deprecated_with_no_deprecated_operations(simple_fastapi_app: FastAPI):
755+
"""Test that ignore_deprecated works correctly when there are no deprecated operations."""
756+
mcp_server_ignore_true = FastApiMCP(simple_fastapi_app, ignore_deprecated=True)
757+
mcp_server_ignore_false = FastApiMCP(simple_fastapi_app, ignore_deprecated=False)
758+
759+
# Both should return the same results when there are no deprecated operations
760+
assert len(mcp_server_ignore_true.tools) == len(mcp_server_ignore_false.tools)
761+
assert len(mcp_server_ignore_true.operation_map) == len(mcp_server_ignore_false.operation_map)
762+
763+
expected_operations = ["list_items", "get_item", "create_item", "update_item", "delete_item", "raise_error"]
764+
765+
for op in expected_operations:
766+
assert op in mcp_server_ignore_true.operation_map
767+
assert op in mcp_server_ignore_false.operation_map
768+
769+
for tool in mcp_server_ignore_true.tools:
770+
assert tool.name in expected_operations
771+
772+
for tool in mcp_server_ignore_false.tools:
773+
assert tool.name in expected_operations
774+
775+
776+
def test_ignore_deprecated_combined_with_filtering(simple_fastapi_app: FastAPI):
777+
"""Test that ignore_deprecated works correctly when combined with other filtering options."""
778+
779+
# Add deprecated operations to the simple app
780+
@simple_fastapi_app.get(
781+
"/deprecated/items/",
782+
response_model=list,
783+
tags=["deprecated"],
784+
operation_id="list_deprecated_items",
785+
deprecated=True,
786+
)
787+
async def list_deprecated_items():
788+
"""[DEPRECATED] List all items (deprecated version)."""
789+
return []
790+
791+
@simple_fastapi_app.get(
792+
"/deprecated/items/{item_id}",
793+
response_model=dict,
794+
tags=["deprecated"],
795+
operation_id="get_deprecated_item",
796+
deprecated=True,
797+
)
798+
async def get_deprecated_item(item_id: int):
799+
"""[DEPRECATED] Get a specific item by its ID (deprecated version)."""
800+
return {"id": item_id}
801+
802+
@simple_fastapi_app.post(
803+
"/deprecated/items/",
804+
response_model=dict,
805+
tags=["deprecated"],
806+
operation_id="create_deprecated_item",
807+
deprecated=True,
808+
)
809+
async def create_deprecated_item():
810+
"""[DEPRECATED] Create a new item in the database (deprecated version)."""
811+
return {"id": 1}
812+
813+
# Test with include_operations
814+
mcp_server = FastApiMCP(
815+
simple_fastapi_app, ignore_deprecated=True, include_operations=["list_items", "get_deprecated_item"]
816+
)
817+
818+
# Should only include list_items since get_deprecated_item is deprecated and ignored
819+
assert len(mcp_server.tools) == 1
820+
assert "list_items" in mcp_server.operation_map
821+
assert "get_deprecated_item" not in mcp_server.operation_map
822+
823+
# Test with include_tags
824+
mcp_server = FastApiMCP(simple_fastapi_app, ignore_deprecated=True, include_tags=["deprecated"])
825+
826+
# Should not include any deprecated operations
827+
assert len(mcp_server.tools) == 0
828+
829+
# Test with exclude_tags
830+
mcp_server = FastApiMCP(simple_fastapi_app, ignore_deprecated=True, exclude_tags=["items"])
831+
832+
# Should only include raise_error since items are excluded and deprecated are ignored
833+
assert len(mcp_server.tools) == 1
834+
assert "raise_error" in mcp_server.operation_map

0 commit comments

Comments
 (0)