Skip to content

Commit 0cddef6

Browse files
authored
Feat/mcp toolbox for db (#293)
* feat: create wrapper to integrate the mcp toolbox for databases into AgentTools Signed-off-by: Casper Nielsen <[email protected]> * fix: remove process_result_fn input -> we always return CallToolResult from MCP servers Signed-off-by: Casper Nielsen <[email protected]> * fix: update docstring Signed-off-by: Casper Nielsen <[email protected]> * test: add test cases for toolbox wrapper Signed-off-by: Casper Nielsen <[email protected]> --------- Signed-off-by: Casper Nielsen <[email protected]>
1 parent 58198e2 commit 0cddef6

File tree

2 files changed

+488
-1
lines changed

2 files changed

+488
-1
lines changed

dapr_agents/tool/base.py

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
import logging
33
from typing import TYPE_CHECKING, Callable, Type, Optional, Any, Dict
44
from inspect import signature, Parameter
5-
from pydantic import BaseModel, Field, ValidationError, model_validator, PrivateAttr
5+
from pydantic import (
6+
BaseModel,
7+
Field,
8+
ValidationError,
9+
model_validator,
10+
PrivateAttr,
11+
create_model,
12+
)
613
from mcp.types import CallToolResult, TextContent
714
from dapr_agents.tool.utils.tool import ToolHelper
815
from dapr_agents.tool.utils.function_calling import to_function_call_definition
@@ -11,6 +18,7 @@
1118
if TYPE_CHECKING:
1219
from mcp.types import Tool as MCPTool
1320
from mcp import ClientSession
21+
from toolbox_core.sync_tool import ToolboxSyncTool
1422

1523
logger = logging.getLogger(__name__)
1624

@@ -190,6 +198,96 @@ async def from_mcp_session(cls, session: "ClientSession") -> list:
190198
session=session,
191199
)
192200

201+
@classmethod
202+
def from_toolbox(
203+
cls,
204+
toolbox_tool: "ToolboxSyncTool",
205+
) -> "AgentTool":
206+
"""
207+
Create an AgentTool from a ToolboxSyncTool.
208+
209+
Args:
210+
toolbox_tool: A ToolboxSyncTool instance from toolbox-core.
211+
212+
Returns:
213+
AgentTool: A ready-to-use AgentTool instance.
214+
"""
215+
tool_name = toolbox_tool._name
216+
tool_description = toolbox_tool._description
217+
218+
def executor(**kwargs: Any) -> Any:
219+
try:
220+
logger.debug(f"Calling Toolbox tool '{tool_name}' with args: {kwargs}")
221+
result = toolbox_tool(**kwargs)
222+
tool_result = CallToolResult(
223+
isError=False,
224+
content=[TextContent(type="text", text=str(result))],
225+
structuredContent={},
226+
)
227+
return tool_result
228+
except (ValidationError, ToolError, Exception) as e:
229+
err_type = type(e).__name__
230+
logger.error(f"{err_type} running tool: {str(e)}")
231+
return CallToolResult(
232+
isError=True,
233+
content=[
234+
TextContent(
235+
type="text",
236+
text=f"{err_type} during Tool Call. Arguments sent to Tool: {str(kwargs)}.\nError: {str(e)}",
237+
)
238+
],
239+
)
240+
241+
tool_args_model = None
242+
try:
243+
params = toolbox_tool._params
244+
if params and len(params) > 0:
245+
field_definitions = {}
246+
for param in params:
247+
annotation = (
248+
param.annotation if hasattr(param, "annotation") else Any
249+
)
250+
if param.required:
251+
field_definitions[param.name] = (annotation, ...)
252+
else:
253+
field_definitions[param.name] = (Optional[annotation], None)
254+
if field_definitions:
255+
tool_args_model = create_model(
256+
f"{tool_name}Args", **field_definitions
257+
)
258+
else:
259+
# Create empty model to avoid infering kwargs onto model
260+
tool_args_model = create_model(f"{tool_name}Args")
261+
except Exception as e:
262+
logger.warning(
263+
f"Failed to create args model for Toolbox tool '{tool_name}': {e}"
264+
)
265+
# Failed to create model from params, fallback to empty model
266+
tool_args_model = create_model(f"{tool_name}Args")
267+
268+
return cls(
269+
name=tool_name,
270+
description=tool_description,
271+
func=executor,
272+
args_model=tool_args_model,
273+
)
274+
275+
@classmethod
276+
def from_toolbox_many(
277+
cls,
278+
toolbox_tools: list,
279+
) -> list:
280+
"""
281+
Batch-create AgentTool objects from a list of ToolboxSyncTool objects.
282+
283+
Args:
284+
toolbox_tools: List of ToolboxSyncTool objects to convert.
285+
286+
Returns:
287+
List[AgentTool]: List of ready-to-use AgentTool objects.
288+
"""
289+
return [cls.from_toolbox(tool) for tool in toolbox_tools]
290+
193291
def model_post_init(self, __context: Any) -> None:
194292
"""
195293
Handles post-initialization logic for both class-based and function-based tools.

0 commit comments

Comments
 (0)