Skip to content

Commit 64c93f4

Browse files
committed
changes followig CR
1 parent 7c6e778 commit 64c93f4

File tree

3 files changed

+58
-27
lines changed

3 files changed

+58
-27
lines changed

docs/03_advanced_usage.md

+7
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ mcp = FastApiMCP(
6262
include_tags=["public"]
6363
)
6464

65+
# Limit maximum allowed length for combined tool name (calculated as server name + operation_id)
66+
# Some vendors throw errors if combined tool names are too long
67+
mcp = FastApiMCP(
68+
app,
69+
max_tool_name_length=25,
70+
)
71+
6572
mcp.mount()
6673
```
6774

fastapi_mcp/server.py

+28-22
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
logger = logging.getLogger(__name__)
1818

19-
FULL_TOOL_NAME_MAX_LENGTH = 55
20-
2119

2220
class FastApiMCP:
2321
def __init__(
@@ -40,6 +38,7 @@ def __init__(
4038
exclude_operations: Optional[List[str]] = None,
4139
include_tags: Optional[List[str]] = None,
4240
exclude_tags: Optional[List[str]] = None,
41+
max_tool_name_length: Optional[int] = None,
4342
):
4443
"""
4544
Create an MCP server from a FastAPI app.
@@ -54,6 +53,8 @@ def __init__(
5453
exclude_operations: List of operation IDs to exclude from MCP tools. Cannot be used with include_operations.
5554
include_tags: List of tags to include as MCP tools. Cannot be used with exclude_tags.
5655
exclude_tags: List of tags to exclude from MCP tools. Cannot be used with include_tags.
56+
max_tool_name_length: Maximum length allowed for tools (some vendors prohibit long names).
57+
Tools breaching this restriction will be filtered out.
5758
"""
5859
# Validate operation and tag filtering options
5960
if include_operations is not None and exclude_operations is not None:
@@ -77,6 +78,7 @@ def __init__(
7778
self._exclude_operations = exclude_operations
7879
self._include_tags = include_tags
7980
self._exclude_tags = exclude_tags
81+
self._max_tool_name_length = max_tool_name_length
8082

8183
self._http_client = http_client or httpx.AsyncClient(
8284
transport=httpx.ASGITransport(app=self.fastapi, raise_app_exceptions=False),
@@ -322,6 +324,7 @@ def _filter_tools(self, tools: List[types.Tool], openapi_schema: Dict[str, Any])
322324
and self._exclude_operations is None
323325
and self._include_tags is None
324326
and self._exclude_tags is None
327+
and self._max_tool_name_length is None
325328
):
326329
return tools
327330

@@ -339,23 +342,6 @@ def _filter_tools(self, tools: List[types.Tool], openapi_schema: Dict[str, Any])
339342
)
340343
continue
341344

342-
operation_full_name = self.get_tool_full_name(operation_id)
343-
if len(operation_full_name) > FULL_TOOL_NAME_MAX_LENGTH:
344-
logger.warning(f"Skipping operation with exceedingly long operationId: {operation_full_name}")
345-
continue
346-
347-
"""
348-
if method not in ["get", "post", "put", "delete", "patch"]:
349-
logger.warning(f"Skipping non-HTTP method: {method.upper()} {path}")
350-
continue
351-
352-
# Get operation metadata
353-
operation_id = operation.get("operationId")
354-
if not operation_id:
355-
logger.warning(f"Skipping operation with no operationId: {method.upper()} {path}, details: {operation}")
356-
continue
357-
"""
358-
359345
tags = operation.get("tags", [])
360346
for tag in tags:
361347
if tag not in operations_by_tag:
@@ -364,11 +350,14 @@ def _filter_tools(self, tools: List[types.Tool], openapi_schema: Dict[str, Any])
364350

365351
operations_to_include = set()
366352

353+
all_operations = {tool.name for tool in tools}
354+
367355
if self._include_operations is not None:
368356
operations_to_include.update(self._include_operations)
369357
elif self._exclude_operations is not None:
370-
all_operations = {tool.name for tool in tools}
371358
operations_to_include.update(all_operations - set(self._exclude_operations))
359+
elif self._max_tool_name_length is not None:
360+
operations_to_include.update(all_operations) # all_operations
372361

373362
if self._include_tags is not None:
374363
for tag in self._include_tags:
@@ -378,9 +367,14 @@ def _filter_tools(self, tools: List[types.Tool], openapi_schema: Dict[str, Any])
378367
for tag in self._exclude_tags:
379368
excluded_operations.update(operations_by_tag.get(tag, []))
380369

381-
all_operations = {tool.name for tool in tools}
382370
operations_to_include.update(all_operations - excluded_operations)
383371

372+
if self._max_tool_name_length is not None:
373+
long_operations = {
374+
tool.name for tool in tools if len(self.get_combined_full_name(tool.name)) > self._max_tool_name_length
375+
}
376+
operations_to_include = operations_to_include - long_operations
377+
384378
filtered_tools = [tool for tool in tools if tool.name in operations_to_include]
385379

386380
if filtered_tools:
@@ -391,5 +385,17 @@ def _filter_tools(self, tools: List[types.Tool], openapi_schema: Dict[str, Any])
391385

392386
return filtered_tools
393387

394-
def get_tool_full_name(self, operation_id: str) -> str:
388+
def get_combined_full_name(self, operation_id: str) -> str:
389+
"""
390+
Combined name consists of server name + operation_id
391+
392+
Args:
393+
operation_id: As defined during creation
394+
395+
Returns:
396+
concatenated string of server name + operation_id
397+
"""
398+
if not self.name:
399+
return operation_id
400+
395401
return f"{self.name}\\{operation_id}"

tests/test_configuration.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def test_default_configuration(simple_fastapi_app: FastAPI):
1717
assert mcp_server._describe_all_responses is False
1818
assert mcp_server._describe_full_response_schema is False
1919

20+
assert mcp_server._max_tool_name_length is None
21+
2022

2123
def test_custom_configuration(simple_fastapi_app: FastAPI):
2224
"""Test a custom configuration of FastApiMCP."""
@@ -372,13 +374,21 @@ async def delete_item(item_id: int):
372374
async def search_items():
373375
return [{"id": 1}]
374376

377+
@app.get("/long_search/", operation_id="search_items_long_id", tags=["search"])
378+
async def search_items_long():
379+
return [{"id": 1}]
380+
381+
# benchmark - no filter
382+
no_filter = FastApiMCP(app)
383+
assert len(no_filter.tools) == 7
384+
375385
# Test include_operations
376386
include_ops_mcp = FastApiMCP(app, include_operations=["get_item", "list_items"])
377387
assert len(include_ops_mcp.tools) == 2
378388
assert {tool.name for tool in include_ops_mcp.tools} == {"get_item", "list_items"}
379389

380390
# Test exclude_operations
381-
exclude_ops_mcp = FastApiMCP(app, exclude_operations=["delete_item", "search_items"])
391+
exclude_ops_mcp = FastApiMCP(app, exclude_operations=["delete_item", "search_items", "search_items_long_id"])
382392
assert len(exclude_ops_mcp.tools) == 4
383393
assert {tool.name for tool in exclude_ops_mcp.tools} == {"get_item", "list_items", "create_item", "update_item"}
384394

@@ -389,13 +399,21 @@ async def search_items():
389399

390400
# Test exclude_tags
391401
exclude_tags_mcp = FastApiMCP(app, exclude_tags=["write", "delete"])
392-
assert len(exclude_tags_mcp.tools) == 3
393-
assert {tool.name for tool in exclude_tags_mcp.tools} == {"get_item", "list_items", "search_items"}
402+
assert len(exclude_tags_mcp.tools) == 4
403+
assert {tool.name for tool in exclude_tags_mcp.tools} == {
404+
"get_item",
405+
"list_items",
406+
"search_items",
407+
"search_items_long_id",
408+
}
394409

395410
# Test combining include_operations and include_tags
396411
combined_include_mcp = FastApiMCP(app, include_operations=["delete_item"], include_tags=["search"])
397-
assert len(combined_include_mcp.tools) == 2
398-
assert {tool.name for tool in combined_include_mcp.tools} == {"delete_item", "search_items"}
412+
assert len(combined_include_mcp.tools) == 3
413+
assert {tool.name for tool in combined_include_mcp.tools} == {"delete_item", "search_items", "search_items_long_id"}
414+
415+
max_long_name_mcp = FastApiMCP(app, name="mcp_server", max_tool_name_length=25)
416+
assert len(max_long_name_mcp.tools) == 6
399417

400418
# Test invalid combinations
401419
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)