Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

tool.py 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import base64
  2. import json
  3. from collections.abc import Generator
  4. from typing import Any
  5. from core.mcp.error import MCPAuthError, MCPConnectionError
  6. from core.mcp.mcp_client import MCPClient
  7. from core.mcp.types import ImageContent, TextContent
  8. from core.tools.__base.tool import Tool
  9. from core.tools.__base.tool_runtime import ToolRuntime
  10. from core.tools.entities.tool_entities import ToolEntity, ToolInvokeMessage, ToolProviderType
  11. class MCPTool(Tool):
  12. def __init__(
  13. self,
  14. entity: ToolEntity,
  15. runtime: ToolRuntime,
  16. tenant_id: str,
  17. icon: str,
  18. server_url: str,
  19. provider_id: str,
  20. headers: dict[str, str] | None = None,
  21. timeout: float | None = None,
  22. sse_read_timeout: float | None = None,
  23. ):
  24. super().__init__(entity, runtime)
  25. self.tenant_id = tenant_id
  26. self.icon = icon
  27. self.server_url = server_url
  28. self.provider_id = provider_id
  29. self.headers = headers or {}
  30. self.timeout = timeout
  31. self.sse_read_timeout = sse_read_timeout
  32. def tool_provider_type(self) -> ToolProviderType:
  33. return ToolProviderType.MCP
  34. def _invoke(
  35. self,
  36. user_id: str,
  37. tool_parameters: dict[str, Any],
  38. conversation_id: str | None = None,
  39. app_id: str | None = None,
  40. message_id: str | None = None,
  41. ) -> Generator[ToolInvokeMessage, None, None]:
  42. from core.tools.errors import ToolInvokeError
  43. try:
  44. with MCPClient(
  45. self.server_url,
  46. self.provider_id,
  47. self.tenant_id,
  48. authed=True,
  49. headers=self.headers,
  50. timeout=self.timeout,
  51. sse_read_timeout=self.sse_read_timeout,
  52. ) as mcp_client:
  53. tool_parameters = self._handle_none_parameter(tool_parameters)
  54. result = mcp_client.invoke_tool(tool_name=self.entity.identity.name, tool_args=tool_parameters)
  55. except MCPAuthError as e:
  56. raise ToolInvokeError("Please auth the tool first") from e
  57. except MCPConnectionError as e:
  58. raise ToolInvokeError(f"Failed to connect to MCP server: {e}") from e
  59. except Exception as e:
  60. raise ToolInvokeError(f"Failed to invoke tool: {e}") from e
  61. for content in result.content:
  62. if isinstance(content, TextContent):
  63. yield from self._process_text_content(content)
  64. elif isinstance(content, ImageContent):
  65. yield self._process_image_content(content)
  66. def _process_text_content(self, content: TextContent) -> Generator[ToolInvokeMessage, None, None]:
  67. """Process text content and yield appropriate messages."""
  68. try:
  69. content_json = json.loads(content.text)
  70. yield from self._process_json_content(content_json)
  71. except json.JSONDecodeError:
  72. yield self.create_text_message(content.text)
  73. def _process_json_content(self, content_json: Any) -> Generator[ToolInvokeMessage, None, None]:
  74. """Process JSON content based on its type."""
  75. if isinstance(content_json, dict):
  76. yield self.create_json_message(content_json)
  77. elif isinstance(content_json, list):
  78. yield from self._process_json_list(content_json)
  79. else:
  80. # For primitive types (str, int, bool, etc.), convert to string
  81. yield self.create_text_message(str(content_json))
  82. def _process_json_list(self, json_list: list) -> Generator[ToolInvokeMessage, None, None]:
  83. """Process a list of JSON items."""
  84. if any(not isinstance(item, dict) for item in json_list):
  85. # If the list contains any non-dict item, treat the entire list as a text message.
  86. yield self.create_text_message(str(json_list))
  87. return
  88. # Otherwise, process each dictionary as a separate JSON message.
  89. for item in json_list:
  90. yield self.create_json_message(item)
  91. def _process_image_content(self, content: ImageContent) -> ToolInvokeMessage:
  92. """Process image content and return a blob message."""
  93. return self.create_blob_message(blob=base64.b64decode(content.data), meta={"mime_type": content.mimeType})
  94. def fork_tool_runtime(self, runtime: ToolRuntime) -> "MCPTool":
  95. return MCPTool(
  96. entity=self.entity,
  97. runtime=runtime,
  98. tenant_id=self.tenant_id,
  99. icon=self.icon,
  100. server_url=self.server_url,
  101. provider_id=self.provider_id,
  102. headers=self.headers,
  103. timeout=self.timeout,
  104. sse_read_timeout=self.sse_read_timeout,
  105. )
  106. def _handle_none_parameter(self, parameter: dict[str, Any]) -> dict[str, Any]:
  107. """
  108. in mcp tool invoke, if the parameter is empty, it will be set to None
  109. """
  110. return {
  111. key: value
  112. for key, value in parameter.items()
  113. if value is not None and not (isinstance(value, str) and value.strip() == "")
  114. }