From 72f635e4e3cf31eefad9663b2026e95247d8747d Mon Sep 17 00:00:00 2001 From: zino Date: Wed, 19 Nov 2025 17:21:33 +0800 Subject: [PATCH] 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: