From a87c7b0064910ef30d962b34d8695ef7c1ea3bb8 Mon Sep 17 00:00:00 2001 From: zino Date: Mon, 17 Nov 2025 17:53:12 +0800 Subject: [PATCH 1/7] feat: add inputs parameter to tool invocation methods for enhanced flexibility --- api/core/agent/fc_agent_runner.py | 8 +++++++- api/core/tools/__base/tool.py | 27 ++++++++++++++++++++------- api/core/tools/mcp_tool/tool.py | 6 ++++++ api/core/tools/tool_engine.py | 6 ++++-- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index dcc1326b33..c05c17b355 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,11 @@ 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 +252,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/tools/__base/tool.py b/api/core/tools/__base/tool.py index 8ca4eabb7a..bd793a140f 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 Exception: + # 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 fbaf31ad09..d11f32e885 100644 --- a/api/core/tools/mcp_tool/tool.py +++ b/api/core/tools/mcp_tool/tool.py @@ -47,7 +47,13 @@ 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]: + # If inputs is provided, flatten its key/value pairs into the tool_parameters. + # This is a conservative merge: only add a key from inputs if it does not already exist in tool_parameters. + for key, value in inputs.items(): + if key not in tool_parameters: + tool_parameters[key] = value result = self.invoke_remote_mcp_tool(tool_parameters) # handle dify tool output for content in result.content: diff --git a/api/core/tools/tool_engine.py b/api/core/tools/tool_engine.py index 13fd579e20..3d0302a93e 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) From 71cd681c91871ecbf570d895c3477cd66c682aef Mon Sep 17 00:00:00 2001 From: Zino Date: Tue, 18 Nov 2025 10:48:33 +0800 Subject: [PATCH 2/7] Update api/core/tools/mcp_tool/tool.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/core/tools/mcp_tool/tool.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/core/tools/mcp_tool/tool.py b/api/core/tools/mcp_tool/tool.py index d11f32e885..8afa9df776 100644 --- a/api/core/tools/mcp_tool/tool.py +++ b/api/core/tools/mcp_tool/tool.py @@ -51,9 +51,10 @@ class MCPTool(Tool): ) -> Generator[ToolInvokeMessage, None, None]: # If inputs is provided, flatten its key/value pairs into the tool_parameters. # This is a conservative merge: only add a key from inputs if it does not already exist in tool_parameters. - for key, value in inputs.items(): - if key not in tool_parameters: - tool_parameters[key] = value + if inputs: + for key, value in inputs.items(): + if key not in tool_parameters: + tool_parameters[key] = value result = self.invoke_remote_mcp_tool(tool_parameters) # handle dify tool output for content in result.content: From 066a923a91afe6eb2ec76452786cd9010976232f Mon Sep 17 00:00:00 2001 From: Zino Date: Tue, 18 Nov 2025 10:48:48 +0800 Subject: [PATCH 3/7] Update api/core/tools/__base/tool.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/core/tools/__base/tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/tools/__base/tool.py b/api/core/tools/__base/tool.py index bd793a140f..6e4e616465 100644 --- a/api/core/tools/__base/tool.py +++ b/api/core/tools/__base/tool.py @@ -71,7 +71,7 @@ class Tool(ABC): sig = inspect.signature(self._invoke) if "inputs" in sig.parameters: invoke_kwargs["inputs"] = inputs - except Exception: + except (ValueError, TypeError): # fallback: Do not pass inputs if reflection fails pass From 72f635e4e3cf31eefad9663b2026e95247d8747d Mon Sep 17 00:00:00 2001 From: zino Date: Wed, 19 Nov 2025 17:21:33 +0800 Subject: [PATCH 4/7] feat: add `_meta` field to `CallToolRequestParams` per MCP protocol Support the protocol-reserved `_meta` parameter for tool invocations, enabling metadata (e.g., progressToken, identityToken) transmission as specified in MCP spec. See: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/basic/index.mdx#general-fields --- api/core/mcp/auth_client.py | 4 ++-- api/core/mcp/mcp_client.py | 4 ++-- api/core/mcp/session/client_session.py | 3 ++- api/core/mcp/types.py | 1 + api/core/tools/mcp_tool/tool.py | 13 ++++--------- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/api/core/mcp/auth_client.py b/api/core/mcp/auth_client.py index 942c8d3c23..21044a744a 100644 --- a/api/core/mcp/auth_client.py +++ b/api/core/mcp/auth_client.py @@ -174,7 +174,7 @@ 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]) -> CallToolResult: """ Invoke a tool on the MCP server with auth retry. @@ -188,4 +188,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 b0e0dab9be..0fd6816493 100644 --- a/api/core/mcp/mcp_client.py +++ b/api/core/mcp/mcp_client.py @@ -96,11 +96,11 @@ 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]) -> 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 fd2062d2e1..fd58c59999 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/mcp_tool/tool.py b/api/core/tools/mcp_tool/tool.py index 8afa9df776..0facb557de 100644 --- a/api/core/tools/mcp_tool/tool.py +++ b/api/core/tools/mcp_tool/tool.py @@ -49,13 +49,7 @@ class MCPTool(Tool): message_id: str | None = None, inputs: dict[str, Any] | None = None, ) -> Generator[ToolInvokeMessage, None, None]: - # If inputs is provided, flatten its key/value pairs into the tool_parameters. - # This is a conservative merge: only add a key from inputs if it does not already exist in tool_parameters. - if inputs: - for key, value in inputs.items(): - if key not in tool_parameters: - tool_parameters[key] = value - 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): @@ -139,7 +133,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]) -> CallToolResult: headers = self.headers.copy() if self.headers else {} tool_parameters = self._handle_none_parameter(tool_parameters) @@ -174,7 +168,8 @@ 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: From 2ef2c1b11381cde03aeb4010d09e4ed4cfe3d04a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:18:21 +0000 Subject: [PATCH 5/7] [autofix.ci] apply automated fixes --- api/core/agent/fc_agent_runner.py | 4 +--- api/core/tools/mcp_tool/tool.py | 5 +++-- api/core/tools/tool_engine.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index c05c17b355..88753ff2de 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -235,9 +235,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): } else: inputs_to_pass = ( - kwargs.get("inputs") - if tool_instance.tool_provider_type() == ToolProviderType.MCP - else None + 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( diff --git a/api/core/tools/mcp_tool/tool.py b/api/core/tools/mcp_tool/tool.py index 0facb557de..c33ea3894c 100644 --- a/api/core/tools/mcp_tool/tool.py +++ b/api/core/tools/mcp_tool/tool.py @@ -168,8 +168,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, - _meta=_meta) + 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 3d0302a93e..a6efd91eee 100644 --- a/api/core/tools/tool_engine.py +++ b/api/core/tools/tool_engine.py @@ -198,7 +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, + inputs: dict[str, Any] | None = None, ) -> Generator[ToolInvokeMessage | ToolInvokeMeta, None, None]: """ Invoke the tool with the given arguments. From f2ea86875d86a4e373a27f179145a613bc838245 Mon Sep 17 00:00:00 2001 From: Zino Date: Mon, 24 Nov 2025 14:57:55 +0800 Subject: [PATCH 6/7] Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/core/mcp/auth_client.py | 2 +- api/core/mcp/mcp_client.py | 2 +- api/core/tools/mcp_tool/tool.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/core/mcp/auth_client.py b/api/core/mcp/auth_client.py index 908ce1b967..bbf18ab780 100644 --- a/api/core/mcp/auth_client.py +++ b/api/core/mcp/auth_client.py @@ -180,7 +180,7 @@ class MCPClientWithAuthRetry(MCPClient): """ return self._execute_with_retry(super().list_tools) - def invoke_tool(self, tool_name: str, tool_args: dict[str, Any], _meta: 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. diff --git a/api/core/mcp/mcp_client.py b/api/core/mcp/mcp_client.py index 0fd6816493..5e9f1f4058 100644 --- a/api/core/mcp/mcp_client.py +++ b/api/core/mcp/mcp_client.py @@ -96,7 +96,7 @@ class MCPClient: response = self._session.list_tools() return response.tools - def invoke_tool(self, tool_name: str, tool_args: dict[str, Any], _meta: 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.") diff --git a/api/core/tools/mcp_tool/tool.py b/api/core/tools/mcp_tool/tool.py index c33ea3894c..60cf2ab925 100644 --- a/api/core/tools/mcp_tool/tool.py +++ b/api/core/tools/mcp_tool/tool.py @@ -133,7 +133,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], _meta: 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) From e569623575397edfd9a6da66ea3044c0199a10fa Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:08:58 +0000 Subject: [PATCH 7/7] [autofix.ci] apply automated fixes --- api/core/mcp/auth_client.py | 4 +++- api/core/mcp/mcp_client.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/core/mcp/auth_client.py b/api/core/mcp/auth_client.py index bbf18ab780..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], _meta: dict[str, Any] | None = None) -> 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. diff --git a/api/core/mcp/mcp_client.py b/api/core/mcp/mcp_client.py index 5e9f1f4058..5929204ecb 100644 --- a/api/core/mcp/mcp_client.py +++ b/api/core/mcp/mcp_client.py @@ -96,7 +96,9 @@ class MCPClient: response = self._session.list_tools() return response.tools - def invoke_tool(self, tool_name: str, tool_args: dict[str, Any], _meta: dict[str, Any] | None = None) -> 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.")