diff --git a/langchain_mcp_adapters/tools.py b/langchain_mcp_adapters/tools.py index a8bf0eb..fb90a9e 100644 --- a/langchain_mcp_adapters/tools.py +++ b/langchain_mcp_adapters/tools.py @@ -251,6 +251,8 @@ async def call_tool( base = tool.annotations.model_dump() if tool.annotations is not None else {} meta = {"_meta": meta} if meta is not None else {} metadata = {**base, **meta} or None + if tool.inputSchema.get("type") == "object": + tool.inputSchema.setdefault("properties", {}) return StructuredTool( name=tool.name, diff --git a/tests/test_tools.py b/tests/test_tools.py index 279da86..ab2e3dc 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -150,6 +150,44 @@ async def test_convert_mcp_tool_to_langchain_tool(): ) +async def test_convert_mcp_tool_with_input_schema_missing_properties(): + tool_input_schema = { + "title": "ToolSchema", + "type": "object", + } + # Mock session and MCP tool + session = AsyncMock() + session.call_tool.return_value = CallToolResult( + content=[TextContent(type="text", text="tool result")], + isError=False, + ) + + mcp_tool = MCPTool( + name="test_tool", + description="Test tool description", + inputSchema=tool_input_schema, + ) + + # Convert MCP tool to LangChain tool + lc_tool = convert_mcp_tool_to_langchain_tool(session, mcp_tool) + + # Verify the converted tool + assert lc_tool.name == "test_tool" + assert lc_tool.description == "Test tool description" + assert lc_tool.args_schema == {**tool_input_schema, "properties": {}} + + # Test calling the tool + result = await lc_tool.ainvoke({"args": {}, "id": "1", "type": "tool_call"}) + + # Verify session.call_tool was called with correct arguments + session.call_tool.assert_called_once_with("test_tool", {}, progress_callback=None) + + # Verify result + assert result == ToolMessage( + content="tool result", name="test_tool", tool_call_id="1" + ) + + async def test_load_mcp_tools(): tool_input_schema = { "properties": {