You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import base64
  2. import json
  3. from collections.abc import Generator
  4. from typing import Any, Optional
  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, ToolParameter, ToolProviderType
  11. class MCPTool(Tool):
  12. tenant_id: str
  13. icon: str
  14. runtime_parameters: Optional[list[ToolParameter]]
  15. server_url: str
  16. provider_id: str
  17. def __init__(
  18. self, entity: ToolEntity, runtime: ToolRuntime, tenant_id: str, icon: str, server_url: str, provider_id: str
  19. ) -> None:
  20. super().__init__(entity, runtime)
  21. self.tenant_id = tenant_id
  22. self.icon = icon
  23. self.runtime_parameters = None
  24. self.server_url = server_url
  25. self.provider_id = provider_id
  26. def tool_provider_type(self) -> ToolProviderType:
  27. return ToolProviderType.MCP
  28. def _invoke(
  29. self,
  30. user_id: str,
  31. tool_parameters: dict[str, Any],
  32. conversation_id: Optional[str] = None,
  33. app_id: Optional[str] = None,
  34. message_id: Optional[str] = None,
  35. ) -> Generator[ToolInvokeMessage, None, None]:
  36. from core.tools.errors import ToolInvokeError
  37. try:
  38. with MCPClient(self.server_url, self.provider_id, self.tenant_id, authed=True) as mcp_client:
  39. tool_parameters = self._handle_none_parameter(tool_parameters)
  40. result = mcp_client.invoke_tool(tool_name=self.entity.identity.name, tool_args=tool_parameters)
  41. except MCPAuthError as e:
  42. raise ToolInvokeError("Please auth the tool first") from e
  43. except MCPConnectionError as e:
  44. raise ToolInvokeError(f"Failed to connect to MCP server: {e}") from e
  45. except Exception as e:
  46. raise ToolInvokeError(f"Failed to invoke tool: {e}") from e
  47. for content in result.content:
  48. if isinstance(content, TextContent):
  49. try:
  50. content_json = json.loads(content.text)
  51. if isinstance(content_json, dict):
  52. yield self.create_json_message(content_json)
  53. elif isinstance(content_json, list):
  54. for item in content_json:
  55. yield self.create_json_message(item)
  56. else:
  57. yield self.create_text_message(content.text)
  58. except json.JSONDecodeError:
  59. yield self.create_text_message(content.text)
  60. elif isinstance(content, ImageContent):
  61. yield self.create_blob_message(
  62. blob=base64.b64decode(content.data), meta={"mime_type": content.mimeType}
  63. )
  64. def fork_tool_runtime(self, runtime: ToolRuntime) -> "MCPTool":
  65. return MCPTool(
  66. entity=self.entity,
  67. runtime=runtime,
  68. tenant_id=self.tenant_id,
  69. icon=self.icon,
  70. server_url=self.server_url,
  71. provider_id=self.provider_id,
  72. )
  73. def _handle_none_parameter(self, parameter: dict[str, Any]) -> dict[str, Any]:
  74. """
  75. in mcp tool invoke, if the parameter is empty, it will be set to None
  76. """
  77. return {
  78. key: value
  79. for key, value in parameter.items()
  80. if value is not None and not (isinstance(value, str) and value.strip() == "")
  81. }