Skip to content

Commit 323f630

Browse files
committed
fix: pass argument properties to mcp tool
1 parent 848f660 commit 323f630

File tree

4 files changed

+462
-16
lines changed

4 files changed

+462
-16
lines changed

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.8.19"
3+
version = "0.8.20"
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: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import copy
12
import logging
23
from contextlib import AsyncExitStack, asynccontextmanager
34
from typing import Any, AsyncGenerator
45

6+
from langchain_core.messages.tool import ToolCall
57
from langchain_core.tools import BaseTool
68
from uipath.agent.models.agent import (
79
AgentMcpResourceConfig,
@@ -10,9 +12,12 @@
1012
)
1113
from uipath.eval.mocks import mockable
1214

13-
from uipath_langchain.agent.tools.base_uipath_structured_tool import (
14-
BaseUiPathStructuredTool,
15+
from uipath_langchain.agent.react.types import AgentGraphState
16+
from uipath_langchain.agent.tools.static_args import handle_static_args
17+
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
18+
StructuredToolWithArgumentProperties,
1519
)
20+
from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType
1621

1722
from ..utils import sanitize_tool_name
1823
from .mcp_client import McpClient, SessionInfoFactory
@@ -73,6 +78,8 @@ async def create_mcp_tools(
7378
f"(dynamic_tools={dynamic_tools.value})"
7479
)
7580

81+
config_tools_by_name = {t.name: t for t in config.available_tools}
82+
7683
if dynamic_tools in (DynamicToolsMode.SCHEMA, DynamicToolsMode.ALL):
7784
logger.info(f"Fetching tools from MCP server '{config.slug}' via list_tools")
7885
result = await mcpClient.list_tools()
@@ -96,24 +103,34 @@ async def create_mcp_tools(
96103
f"Filtered to {len(server_tools)} tools matching availableTools"
97104
)
98105

99-
mcp_tools = [
100-
AgentMcpTool(
101-
name=tool.name,
102-
description=tool.description or "",
103-
input_schema=tool.inputSchema,
104-
output_schema=tool.outputSchema,
106+
mcp_tools = []
107+
for tool in server_tools:
108+
config_tool = config_tools_by_name.get(tool.name)
109+
argument_properties = config_tool.argument_properties if config_tool else {}
110+
server_schema = tool.inputSchema
111+
if config_tool:
112+
server_schema = _merge_descriptions_from_config(
113+
server_schema, config_tool.input_schema
114+
)
115+
mcp_tools.append(
116+
AgentMcpTool(
117+
name=tool.name,
118+
description=tool.description or "",
119+
inputSchema=server_schema,
120+
outputSchema=tool.outputSchema,
121+
argumentProperties=argument_properties,
122+
)
105123
)
106-
for tool in server_tools
107-
]
108124
else:
109125
mcp_tools = config.available_tools
110126
logger.info(
111127
f"Using {len(mcp_tools)} tools from resource config for "
112128
f"server '{config.slug}'"
113129
)
114130

115-
return [
116-
BaseUiPathStructuredTool(
131+
tools: list[BaseTool] = []
132+
for mcp_tool in mcp_tools:
133+
tool = StructuredToolWithArgumentProperties(
117134
name=sanitize_tool_name(mcp_tool.name),
118135
description=mcp_tool.description,
119136
args_schema=mcp_tool.input_schema,
@@ -124,9 +141,12 @@ async def create_mcp_tools(
124141
"folder_path": config.folder_path,
125142
"slug": config.slug,
126143
},
144+
argument_properties=mcp_tool.argument_properties,
127145
)
128-
for mcp_tool in mcp_tools
129-
]
146+
if mcp_tool.argument_properties:
147+
tool.set_tool_wrappers(awrapper=_mcp_tool_wrapper)
148+
tools.append(tool)
149+
return tools
130150

131151

132152
def build_mcp_tool(mcp_tool: AgentMcpTool, mcpClient: McpClient) -> Any:
@@ -218,3 +238,48 @@ async def create_mcp_tools_and_clients(
218238
)
219239

220240
return tools, clients
241+
242+
243+
async def _mcp_tool_wrapper(
244+
tool: BaseTool,
245+
call: ToolCall,
246+
state: AgentGraphState,
247+
) -> ToolWrapperReturnType:
248+
"""Wrapper that injects static argument values before MCP tool execution."""
249+
call["args"] = handle_static_args(tool, state, call["args"])
250+
return await tool.ainvoke(call)
251+
252+
253+
def _merge_descriptions_from_config(
254+
server_schema: dict[str, Any],
255+
config_schema: dict[str, Any],
256+
) -> dict[str, Any]:
257+
"""Merge property descriptions from config schema into server schema.
258+
259+
For each property that exists in both schemas, if the config schema
260+
has a description, it overwrites the server schema's description.
261+
This preserves prompt overrides that were configured in agent.json.
262+
263+
Args:
264+
server_schema: The fresh schema from the MCP server.
265+
config_schema: The schema from the agent config with prompt overrides.
266+
267+
Returns:
268+
A copy of the server schema with config descriptions merged in.
269+
"""
270+
server_props = server_schema.get("properties", {})
271+
config_props = config_schema.get("properties", {})
272+
273+
if not config_props or not server_props:
274+
return server_schema
275+
276+
merged_schema = copy.deepcopy(server_schema)
277+
merged_props = merged_schema["properties"]
278+
279+
for prop_name, config_prop in config_props.items():
280+
if prop_name not in merged_props:
281+
continue
282+
if "description" in config_prop:
283+
merged_props[prop_name]["description"] = config_prop["description"]
284+
285+
return merged_schema

0 commit comments

Comments
 (0)