mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-25 01:02:15 +00:00
fix(mcp): return camelCase tool keys from /v1/mcp/tools after the SDK 2 upgrade
SDK 2 spells the Tool model's Python attributes in snake_case behind camelCase aliases, so dumping attribute names handed scripts input_schema and output_schema instead of the inputSchema and outputSchema v1.102.0 returned. Dump each tool by its MCP wire aliases, as the other list routes do, and pin the shape with a regression test. Also drop an unused tools dict in the Responses MCP stream iterator.
This commit is contained in:
parent
3d26a29a1a
commit
51aa021c6e
3 changed files with 27 additions and 11 deletions
|
|
@ -983,7 +983,7 @@ if MCP_AVAILABLE:
|
|||
mcp_server_auth_headers=None,
|
||||
)
|
||||
tools: Final = listing.tools
|
||||
dumped_tools: Final = [dict(tool) for tool in tools]
|
||||
dumped_tools: Final = [tool.model_dump(by_alias=True) for tool in tools]
|
||||
|
||||
return {"tools": dumped_tools}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,16 +91,6 @@ async def create_mcp_list_tools_events(
|
|||
# Use the pre-processed MCP tools that were already fetched, filtered, and deduplicated by the parent
|
||||
filtered_mcp_tools: Final = pre_processed_mcp_tools
|
||||
|
||||
# Convert tools to dict format for the event
|
||||
_mcp_tools_dict: Final = [
|
||||
tool.model_dump()
|
||||
if hasattr(tool, "model_dump") and callable(getattr(tool, "model_dump", None))
|
||||
else tool.__dict__
|
||||
if hasattr(tool, "__dict__")
|
||||
else {"name": getattr(tool, "name", str(tool))}
|
||||
for tool in filtered_mcp_tools
|
||||
]
|
||||
|
||||
# Emit list tools completed event
|
||||
completed_event: Final = MCPListToolsCompletedEvent(
|
||||
type=ResponsesAPIStreamEvents.MCP_LIST_TOOLS_COMPLETED,
|
||||
|
|
|
|||
|
|
@ -7824,3 +7824,29 @@ class TestDeleteMCPGatewaySessions:
|
|||
assert result.terminated_sessions == 2
|
||||
assert {s.user_id for s in result.sessions} == {"bob"}
|
||||
assert "sk-live-bob" not in result.model_dump_json()
|
||||
|
||||
|
||||
class TestGetMcpToolsWireShape:
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_mcp_tools_returns_each_tool_in_mcp_wire_spelling(self):
|
||||
"""GET /v1/mcp/tools hands scripts each tool in the MCP wire spelling (`inputSchema`,
|
||||
`outputSchema`, `_meta`), the shape v1.102.0 returned and the shape /mcp-rest/tools/list and the
|
||||
JSON-RPC tools/list still return. SDK 2 renamed the Tool model's Python attributes to snake_case
|
||||
behind camelCase aliases, so dumping attribute names leaked `input_schema` to every reader."""
|
||||
from mcp.types import ListToolsResult, Tool
|
||||
|
||||
add_schema = {"type": "object", "properties": {"a": {"type": "integer"}}, "required": ["a"]}
|
||||
listed = ListToolsResult(
|
||||
tools=[Tool(name="add", description="Add", inputSchema=add_schema, outputSchema={"type": "integer"})]
|
||||
)
|
||||
with patch(
|
||||
"litellm.proxy._experimental.mcp_server.server._list_mcp_tools",
|
||||
AsyncMock(return_value=listed),
|
||||
):
|
||||
result = await mgmt_endpoints.get_mcp_tools(user_api_key_dict=generate_mock_user_api_key_auth())
|
||||
|
||||
(tool,) = result["tools"]
|
||||
assert tool["inputSchema"] == add_schema
|
||||
assert tool["outputSchema"] == {"type": "integer"}
|
||||
assert "_meta" in tool
|
||||
assert not {"input_schema", "output_schema", "meta"} & tool.keys()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue