@@ -27,6 +27,10 @@ def __init__(
27
27
describe_all_responses : bool = False ,
28
28
describe_full_response_schema : bool = False ,
29
29
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 ,
30
34
):
31
35
"""
32
36
Create an MCP server from a FastAPI app.
@@ -42,7 +46,17 @@ def __init__(
42
46
describe_full_response_schema: Whether to include full json schema for responses in tool descriptions
43
47
http_client: Optional HTTP client to use for API calls. If not provided, a new httpx.AsyncClient will be created.
44
48
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.
45
53
"""
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" )
46
60
47
61
self .operation_map : Dict [str , Dict [str , Any ]]
48
62
self .tools : List [types .Tool ]
@@ -55,6 +69,10 @@ def __init__(
55
69
self ._base_url = base_url
56
70
self ._describe_all_responses = describe_all_responses
57
71
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
58
76
59
77
self ._http_client = http_client or httpx .AsyncClient ()
60
78
@@ -71,12 +89,15 @@ def setup_server(self) -> None:
71
89
)
72
90
73
91
# 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 (
75
93
openapi_schema ,
76
94
describe_all_responses = self ._describe_all_responses ,
77
95
describe_full_response_schema = self ._describe_full_response_schema ,
78
96
)
79
97
98
+ # Filter tools based on operation IDs and tags
99
+ self .tools = self ._filter_tools (all_tools , openapi_schema )
100
+
80
101
# Determine base URL if not provided
81
102
if not self ._base_url :
82
103
# Try to determine the base URL from FastAPI config
@@ -266,3 +287,67 @@ async def _request(
266
287
return await client .patch (url , params = query , headers = headers , json = body )
267
288
else :
268
289
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