Skip to content

Commit 034c5f3

Browse files
committed
fix: pass argument properties to mcp tool
1 parent a3e89aa commit 034c5f3

4 files changed

Lines changed: 153 additions & 27 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.10.15"
3+
version = "0.10.16"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/agent/tools/mcp/mcp_tool.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
)
1111
from uipath.eval.mocks import mockable
1212

13-
from uipath_langchain.agent.tools.base_uipath_structured_tool import (
14-
BaseUiPathStructuredTool,
13+
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
14+
StructuredToolWithArgumentProperties,
1515
)
1616

1717
from ..utils import sanitize_tool_name
@@ -73,6 +73,8 @@ async def create_mcp_tools(
7373
f"(dynamic_tools={dynamic_tools.value})"
7474
)
7575

76+
config_tools_by_name = {t.name: t for t in config.available_tools}
77+
7678
if dynamic_tools in (DynamicToolsMode.SCHEMA, DynamicToolsMode.ALL):
7779
logger.info(f"Fetching tools from MCP server '{config.slug}' via list_tools")
7880
result = await mcpClient.list_tools()
@@ -96,37 +98,45 @@ async def create_mcp_tools(
9698
f"Filtered to {len(server_tools)} tools matching availableTools"
9799
)
98100

99-
mcp_tools = [
100-
AgentMcpTool(
101-
name=tool.name,
102-
description=tool.description or "",
103-
input_schema=tool.inputSchema,
104-
output_schema=tool.outputSchema,
101+
mcp_tools = []
102+
for tool in server_tools:
103+
config_tool = config_tools_by_name.get(tool.name)
104+
argument_properties = config_tool.argument_properties if config_tool else {}
105+
mcp_tools.append(
106+
AgentMcpTool(
107+
name=tool.name,
108+
description=tool.description or "",
109+
input_schema=tool.inputSchema,
110+
output_schema=tool.outputSchema,
111+
argument_properties=argument_properties,
112+
)
105113
)
106-
for tool in server_tools
107-
]
108114
else:
109115
mcp_tools = config.available_tools
110116
logger.info(
111117
f"Using {len(mcp_tools)} tools from resource config for "
112118
f"server '{config.slug}'"
113119
)
114120

115-
return [
116-
BaseUiPathStructuredTool(
117-
name=sanitize_tool_name(mcp_tool.name),
118-
description=mcp_tool.description,
119-
args_schema=mcp_tool.input_schema,
120-
coroutine=build_mcp_tool(mcp_tool, mcpClient),
121-
metadata={
122-
"tool_type": "mcp",
123-
"display_name": mcp_tool.name,
124-
"folder_path": config.folder_path,
125-
"slug": config.slug,
126-
},
121+
tools: list[BaseTool] = []
122+
for mcp_tool in mcp_tools:
123+
tools.append(
124+
StructuredToolWithArgumentProperties(
125+
name=sanitize_tool_name(mcp_tool.name),
126+
description=mcp_tool.description,
127+
args_schema=mcp_tool.input_schema,
128+
coroutine=build_mcp_tool(mcp_tool, mcpClient),
129+
output_type=Any,
130+
metadata={
131+
"tool_type": "mcp",
132+
"display_name": mcp_tool.name,
133+
"folder_path": config.folder_path,
134+
"slug": config.slug,
135+
},
136+
argument_properties=mcp_tool.argument_properties,
137+
)
127138
)
128-
for mcp_tool in mcp_tools
129-
]
139+
return tools
130140

131141

132142
def build_mcp_tool(mcp_tool: AgentMcpTool, mcpClient: McpClient) -> Any:

tests/agent/tools/test_mcp/test_mcp_tool.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import json
44
import logging
5-
from typing import Any
5+
from typing import Any, cast
66
from unittest.mock import AsyncMock, MagicMock, patch
77

88
import pytest
@@ -21,6 +21,9 @@
2121
create_mcp_tools_and_clients,
2222
open_mcp_tools,
2323
)
24+
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
25+
StructuredToolWithArgumentProperties,
26+
)
2427

2528
logger = logging.getLogger(__name__)
2629

@@ -1005,3 +1008,116 @@ async def test_disposes_clients_on_exception(self, mcp_config):
10051008
raise RuntimeError("boom")
10061009

10071010
mock_client.dispose.assert_awaited_once()
1011+
1012+
1013+
class TestMcpToolArgumentProperties:
1014+
"""Test that argument_properties from config are applied to MCP tools."""
1015+
1016+
@pytest.fixture
1017+
def mock_mcp_client(self):
1018+
return MagicMock(spec=McpClient)
1019+
1020+
@pytest.mark.asyncio
1021+
async def test_none_mode_passes_argument_properties_to_tool(self, mock_mcp_client):
1022+
"""In none mode, argument_properties from config must reach the built tool."""
1023+
resource = AgentMcpResourceConfig(
1024+
name="test_server",
1025+
description="Test",
1026+
folder_path="/Shared",
1027+
slug="test",
1028+
available_tools=[
1029+
AgentMcpTool(
1030+
name="divide",
1031+
description="Divide two numbers",
1032+
input_schema={
1033+
"type": "object",
1034+
"properties": {
1035+
"a": {"type": "number"},
1036+
"b": {"type": "number"},
1037+
},
1038+
"required": ["a", "b"],
1039+
},
1040+
argument_properties={
1041+
"$['a']": {
1042+
"variant": "static",
1043+
"value": 76,
1044+
"isSensitive": False,
1045+
}
1046+
},
1047+
),
1048+
],
1049+
)
1050+
1051+
tools = await create_mcp_tools(resource, mock_mcp_client)
1052+
1053+
assert len(tools) == 1
1054+
tool = tools[0]
1055+
assert hasattr(tool, "argument_properties")
1056+
assert "$['a']" in tool.argument_properties
1057+
1058+
@pytest.mark.asyncio
1059+
async def test_all_mode_carries_over_argument_properties_for_matching_tools(
1060+
self, mock_mcp_client
1061+
):
1062+
"""In all mode, argument_properties from config must attach to matching server tools."""
1063+
mock_mcp_client.list_tools = AsyncMock(
1064+
return_value=ListToolsResult(
1065+
tools=[
1066+
Tool(
1067+
name="divide",
1068+
description="Divide from server",
1069+
inputSchema={
1070+
"type": "object",
1071+
"properties": {
1072+
"a": {"type": "number"},
1073+
"b": {"type": "number"},
1074+
},
1075+
},
1076+
),
1077+
Tool(
1078+
name="new_tool",
1079+
description="New tool not in config",
1080+
inputSchema={"type": "object", "properties": {}},
1081+
),
1082+
]
1083+
)
1084+
)
1085+
1086+
resource = AgentMcpResourceConfig(
1087+
name="test_server",
1088+
description="Test",
1089+
folder_path="/Shared",
1090+
slug="test",
1091+
dynamic_tools=DynamicToolsMode.ALL,
1092+
available_tools=[
1093+
AgentMcpTool(
1094+
name="divide",
1095+
description="Divide (stale)",
1096+
input_schema={"type": "object", "properties": {}},
1097+
argument_properties={
1098+
"$['a']": {
1099+
"variant": "static",
1100+
"value": 76,
1101+
"isSensitive": False,
1102+
}
1103+
},
1104+
),
1105+
],
1106+
)
1107+
1108+
tools = await create_mcp_tools(resource, mock_mcp_client)
1109+
1110+
assert len(tools) == 2
1111+
divide_tool = next(
1112+
cast(StructuredToolWithArgumentProperties, t)
1113+
for t in tools
1114+
if t.name == "divide"
1115+
)
1116+
new_tool = next(
1117+
cast(StructuredToolWithArgumentProperties, t)
1118+
for t in tools
1119+
if t.name == "new_tool"
1120+
)
1121+
1122+
assert "$['a']" in divide_tool.argument_properties
1123+
assert not new_tool.argument_properties

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)