diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index dcc1326b33..88753ff2de 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -23,7 +23,7 @@ from core.model_runtime.entities import ( ) from core.model_runtime.entities.message_entities import ImagePromptMessageContent, PromptMessageContentUnionTypes from core.prompt.agent_history_prompt_transform import AgentHistoryPromptTransform -from core.tools.entities.tool_entities import ToolInvokeMeta +from core.tools.entities.tool_entities import ToolInvokeMeta, ToolProviderType from core.tools.tool_engine import ToolEngine from models.model import Message @@ -234,6 +234,9 @@ class FunctionCallAgentRunner(BaseAgentRunner): "meta": ToolInvokeMeta.error_instance(f"there is not a tool named {tool_call_name}").to_dict(), } else: + inputs_to_pass = ( + kwargs.get("inputs") if tool_instance.tool_provider_type() == ToolProviderType.MCP else None + ) # invoke tool tool_invoke_response, message_files, tool_invoke_meta = ToolEngine.agent_invoke( tool=tool_instance, @@ -247,6 +250,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): app_id=self.application_generate_entity.app_config.app_id, message_id=self.message.id, conversation_id=self.conversation.id, + inputs=inputs_to_pass, ) # publish files for message_file_id in message_files: diff --git a/api/core/mcp/auth_client.py b/api/core/mcp/auth_client.py index d8724b8de5..7613c2733c 100644 --- a/api/core/mcp/auth_client.py +++ b/api/core/mcp/auth_client.py @@ -180,7 +180,9 @@ class MCPClientWithAuthRetry(MCPClient): """ return self._execute_with_retry(super().list_tools) - def invoke_tool(self, tool_name: str, tool_args: dict[str, Any]) -> CallToolResult: + def invoke_tool( + self, tool_name: str, tool_args: dict[str, Any], _meta: dict[str, Any] | None = None + ) -> CallToolResult: """ Invoke a tool on the MCP server with auth retry. @@ -194,4 +196,4 @@ class MCPClientWithAuthRetry(MCPClient): Raises: MCPAuthError: If authentication fails after retries """ - return self._execute_with_retry(super().invoke_tool, tool_name, tool_args) + return self._execute_with_retry(super().invoke_tool, tool_name, tool_args, _meta) diff --git a/api/core/mcp/mcp_client.py b/api/core/mcp/mcp_client.py index 2b0645b558..2b30bfc21b 100644 --- a/api/core/mcp/mcp_client.py +++ b/api/core/mcp/mcp_client.py @@ -96,11 +96,13 @@ class MCPClient: response = self._session.list_tools() return response.tools - def invoke_tool(self, tool_name: str, tool_args: dict[str, Any]) -> CallToolResult: + def invoke_tool( + self, tool_name: str, tool_args: dict[str, Any], _meta: dict[str, Any] | None = None + ) -> CallToolResult: """Call a tool""" if not self._session: raise ValueError("Session not initialized.") - return self._session.call_tool(tool_name, tool_args) + return self._session.call_tool(tool_name, tool_args, _meta=_meta) def cleanup(self): """Clean up resources""" diff --git a/api/core/mcp/session/client_session.py b/api/core/mcp/session/client_session.py index d684fe0dd7..33be00365e 100644 --- a/api/core/mcp/session/client_session.py +++ b/api/core/mcp/session/client_session.py @@ -248,6 +248,7 @@ class ClientSession( self, name: str, arguments: dict[str, Any] | None = None, + _meta: dict[str, Any] | None = None, read_timeout_seconds: timedelta | None = None, ) -> types.CallToolResult: """Send a tools/call request.""" @@ -256,7 +257,7 @@ class ClientSession( types.ClientRequest( types.CallToolRequest( method="tools/call", - params=types.CallToolRequestParams(name=name, arguments=arguments), + params=types.CallToolRequestParams(name=name, arguments=arguments, _meta=_meta), ) ), types.CallToolResult, diff --git a/api/core/mcp/types.py b/api/core/mcp/types.py index 335c6a5cbc..f8e5edb770 100644 --- a/api/core/mcp/types.py +++ b/api/core/mcp/types.py @@ -855,6 +855,7 @@ class CallToolRequestParams(RequestParams): name: str arguments: dict[str, Any] | None = None + _meta: dict[str, Any] | None = None model_config = ConfigDict(extra="allow") diff --git a/api/core/tools/__base/tool.py b/api/core/tools/__base/tool.py index 8ca4eabb7a..6e4e616465 100644 --- a/api/core/tools/__base/tool.py +++ b/api/core/tools/__base/tool.py @@ -1,3 +1,4 @@ +import inspect from abc import ABC, abstractmethod from collections.abc import Generator from copy import deepcopy @@ -49,6 +50,7 @@ class Tool(ABC): conversation_id: str | None = None, app_id: str | None = None, message_id: str | None = None, + inputs: dict[str, Any] | None = None, ) -> Generator[ToolInvokeMessage]: if self.runtime and self.runtime.runtime_parameters: tool_parameters.update(self.runtime.runtime_parameters) @@ -56,13 +58,24 @@ class Tool(ABC): # try parse tool parameters into the correct type tool_parameters = self._transform_tool_parameters_type(tool_parameters) - result = self._invoke( - user_id=user_id, - tool_parameters=tool_parameters, - conversation_id=conversation_id, - app_id=app_id, - message_id=message_id, - ) + # Construct the call parameter and pass in inputs only when the _invoke of the subclass accepts inputs + invoke_kwargs = { + "user_id": user_id, + "tool_parameters": tool_parameters, + "conversation_id": conversation_id, + "app_id": app_id, + "message_id": message_id, + } + if inputs is not None: + try: + sig = inspect.signature(self._invoke) + if "inputs" in sig.parameters: + invoke_kwargs["inputs"] = inputs + except (ValueError, TypeError): + # fallback: Do not pass inputs if reflection fails + pass + + result = self._invoke(**invoke_kwargs) if isinstance(result, ToolInvokeMessage): diff --git a/api/core/tools/mcp_tool/tool.py b/api/core/tools/mcp_tool/tool.py index 96917045e3..38696219bb 100644 --- a/api/core/tools/mcp_tool/tool.py +++ b/api/core/tools/mcp_tool/tool.py @@ -55,8 +55,9 @@ class MCPTool(Tool): conversation_id: str | None = None, app_id: str | None = None, message_id: str | None = None, + inputs: dict[str, Any] | None = None, ) -> Generator[ToolInvokeMessage, None, None]: - result = self.invoke_remote_mcp_tool(tool_parameters) + result = self.invoke_remote_mcp_tool(tool_parameters, _meta=inputs) # handle dify tool output for content in result.content: if isinstance(content, TextContent): @@ -141,7 +142,7 @@ class MCPTool(Tool): if value is not None and not (isinstance(value, str) and value.strip() == "") } - def invoke_remote_mcp_tool(self, tool_parameters: dict[str, Any]) -> CallToolResult: + def invoke_remote_mcp_tool(self, tool_parameters: dict[str, Any], _meta: dict[str, Any] | None) -> CallToolResult: headers = self.headers.copy() if self.headers else {} tool_parameters = self._handle_none_parameter(tool_parameters) @@ -176,7 +177,9 @@ class MCPTool(Tool): sse_read_timeout=self.sse_read_timeout, provider_entity=provider_entity, ) as mcp_client: - return mcp_client.invoke_tool(tool_name=self.entity.identity.name, tool_args=tool_parameters) + return mcp_client.invoke_tool( + tool_name=self.entity.identity.name, tool_args=tool_parameters, _meta=_meta + ) except MCPConnectionError as e: raise ToolInvokeError(f"Failed to connect to MCP server: {e}") from e except Exception as e: diff --git a/api/core/tools/tool_engine.py b/api/core/tools/tool_engine.py index 13fd579e20..a6efd91eee 100644 --- a/api/core/tools/tool_engine.py +++ b/api/core/tools/tool_engine.py @@ -55,6 +55,7 @@ class ToolEngine: conversation_id: str | None = None, app_id: str | None = None, message_id: str | None = None, + inputs: dict[str, Any] | None = None, ) -> tuple[str, list[str], ToolInvokeMeta]: """ Agent invokes the tool with the given arguments. @@ -79,7 +80,7 @@ class ToolEngine: # hit the callback handler agent_tool_callback.on_tool_start(tool_name=tool.entity.identity.name, tool_inputs=tool_parameters) - messages = ToolEngine._invoke(tool, tool_parameters, user_id, conversation_id, app_id, message_id) + messages = ToolEngine._invoke(tool, tool_parameters, user_id, conversation_id, app_id, message_id, inputs) invocation_meta_dict: dict[str, ToolInvokeMeta] = {} def message_callback( @@ -197,6 +198,7 @@ class ToolEngine: conversation_id: str | None = None, app_id: str | None = None, message_id: str | None = None, + inputs: dict[str, Any] | None = None, ) -> Generator[ToolInvokeMessage | ToolInvokeMeta, None, None]: """ Invoke the tool with the given arguments. @@ -214,7 +216,7 @@ class ToolEngine: }, ) try: - yield from tool.invoke(user_id, tool_parameters, conversation_id, app_id, message_id) + yield from tool.invoke(user_id, tool_parameters, conversation_id, app_id, message_id, inputs) except Exception as e: meta.error = str(e) raise ToolEngineInvokeError(meta)