@@ -27,6 +27,10 @@ def __init__(
2727 describe_all_responses : bool = False ,
2828 describe_full_response_schema : bool = False ,
2929 http_client : Optional [AsyncClientProtocol ] = None ,
30+ include_operations : Optional [List [str ]] = None ,
31+ exclude_operations : Optional [List [str ]] = None ,
32+ include_tags : Optional [List [str ]] = None ,
33+ exclude_tags : Optional [List [str ]] = None ,
3034 ):
3135 """
3236 Create an MCP server from a FastAPI app.
@@ -42,7 +46,17 @@ def __init__(
4246 describe_full_response_schema: Whether to include full json schema for responses in tool descriptions
4347 http_client: Optional HTTP client to use for API calls. If not provided, a new httpx.AsyncClient will be created.
4448 This is primarily for testing purposes.
49+ include_operations: List of operation IDs to include as MCP tools. Cannot be used with exclude_operations.
50+ exclude_operations: List of operation IDs to exclude from MCP tools. Cannot be used with include_operations.
51+ include_tags: List of tags to include as MCP tools. Cannot be used with exclude_tags.
52+ exclude_tags: List of tags to exclude from MCP tools. Cannot be used with include_tags.
4553 """
54+ # Validate operation and tag filtering options
55+ if include_operations is not None and exclude_operations is not None :
56+ raise ValueError ("Cannot specify both include_operations and exclude_operations" )
57+
58+ if include_tags is not None and exclude_tags is not None :
59+ raise ValueError ("Cannot specify both include_tags and exclude_tags" )
4660
4761 self .operation_map : Dict [str , Dict [str , Any ]]
4862 self .tools : List [types .Tool ]
@@ -55,6 +69,10 @@ def __init__(
5569 self ._base_url = base_url
5670 self ._describe_all_responses = describe_all_responses
5771 self ._describe_full_response_schema = describe_full_response_schema
72+ self ._include_operations = include_operations
73+ self ._exclude_operations = exclude_operations
74+ self ._include_tags = include_tags
75+ self ._exclude_tags = exclude_tags
5876
5977 self ._http_client = http_client or httpx .AsyncClient ()
6078
@@ -71,12 +89,15 @@ def setup_server(self) -> None:
7189 )
7290
7391 # Convert OpenAPI schema to MCP tools
74- self . tools , self .operation_map = convert_openapi_to_mcp_tools (
92+ all_tools , self .operation_map = convert_openapi_to_mcp_tools (
7593 openapi_schema ,
7694 describe_all_responses = self ._describe_all_responses ,
7795 describe_full_response_schema = self ._describe_full_response_schema ,
7896 )
7997
98+ # Filter tools based on operation IDs and tags
99+ self .tools = self ._filter_tools (all_tools , openapi_schema )
100+
80101 # Determine base URL if not provided
81102 if not self ._base_url :
82103 # Try to determine the base URL from FastAPI config
@@ -266,3 +287,67 @@ async def _request(
266287 return await client .patch (url , params = query , headers = headers , json = body )
267288 else :
268289 raise ValueError (f"Unsupported HTTP method: { method } " )
290+
291+ def _filter_tools (self , tools : List [types .Tool ], openapi_schema : Dict [str , Any ]) -> List [types .Tool ]:
292+ """
293+ Filter tools based on operation IDs and tags.
294+
295+ Args:
296+ tools: List of tools to filter
297+ openapi_schema: The OpenAPI schema
298+
299+ Returns:
300+ Filtered list of tools
301+ """
302+ if (
303+ self ._include_operations is None
304+ and self ._exclude_operations is None
305+ and self ._include_tags is None
306+ and self ._exclude_tags is None
307+ ):
308+ return tools
309+
310+ operations_by_tag : Dict [str , List [str ]] = {}
311+ for path , path_item in openapi_schema .get ("paths" , {}).items ():
312+ for method , operation in path_item .items ():
313+ if method not in ["get" , "post" , "put" , "delete" , "patch" ]:
314+ continue
315+
316+ operation_id = operation .get ("operationId" )
317+ if not operation_id :
318+ continue
319+
320+ tags = operation .get ("tags" , [])
321+ for tag in tags :
322+ if tag not in operations_by_tag :
323+ operations_by_tag [tag ] = []
324+ operations_by_tag [tag ].append (operation_id )
325+
326+ operations_to_include = set ()
327+
328+ if self ._include_operations is not None :
329+ operations_to_include .update (self ._include_operations )
330+ elif self ._exclude_operations is not None :
331+ all_operations = {tool .name for tool in tools }
332+ operations_to_include .update (all_operations - set (self ._exclude_operations ))
333+
334+ if self ._include_tags is not None :
335+ for tag in self ._include_tags :
336+ operations_to_include .update (operations_by_tag .get (tag , []))
337+ elif self ._exclude_tags is not None :
338+ excluded_operations = set ()
339+ for tag in self ._exclude_tags :
340+ excluded_operations .update (operations_by_tag .get (tag , []))
341+
342+ all_operations = {tool .name for tool in tools }
343+ operations_to_include .update (all_operations - excluded_operations )
344+
345+ filtered_tools = [tool for tool in tools if tool .name in operations_to_include ]
346+
347+ if filtered_tools :
348+ filtered_operation_ids = {tool .name for tool in filtered_tools }
349+ self .operation_map = {
350+ op_id : details for op_id , details in self .operation_map .items () if op_id in filtered_operation_ids
351+ }
352+
353+ return filtered_tools
0 commit comments