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
This commit is contained in:
zino 2025-11-19 17:21:33 +08:00
parent 4819e324da
commit 72f635e4e3
5 changed files with 11 additions and 14 deletions

View File

@ -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)

View File

@ -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"""

View File

@ -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,

View File

@ -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")

View File

@ -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: