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.

utils.py 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import json
  2. import httpx
  3. from configs import dify_config
  4. from core.mcp.types import ErrorData, JSONRPCError
  5. from core.model_runtime.utils.encoders import jsonable_encoder
  6. HTTP_REQUEST_NODE_SSL_VERIFY = dify_config.HTTP_REQUEST_NODE_SSL_VERIFY
  7. STATUS_FORCELIST = [429, 500, 502, 503, 504]
  8. def create_ssrf_proxy_mcp_http_client(
  9. headers: dict[str, str] | None = None,
  10. timeout: httpx.Timeout | None = None,
  11. ) -> httpx.Client:
  12. """Create an HTTPX client with SSRF proxy configuration for MCP connections.
  13. Args:
  14. headers: Optional headers to include in the client
  15. timeout: Optional timeout configuration
  16. Returns:
  17. Configured httpx.Client with proxy settings
  18. """
  19. if dify_config.SSRF_PROXY_ALL_URL:
  20. return httpx.Client(
  21. verify=HTTP_REQUEST_NODE_SSL_VERIFY,
  22. headers=headers or {},
  23. timeout=timeout,
  24. follow_redirects=True,
  25. proxy=dify_config.SSRF_PROXY_ALL_URL,
  26. )
  27. elif dify_config.SSRF_PROXY_HTTP_URL and dify_config.SSRF_PROXY_HTTPS_URL:
  28. proxy_mounts = {
  29. "http://": httpx.HTTPTransport(proxy=dify_config.SSRF_PROXY_HTTP_URL, verify=HTTP_REQUEST_NODE_SSL_VERIFY),
  30. "https://": httpx.HTTPTransport(
  31. proxy=dify_config.SSRF_PROXY_HTTPS_URL, verify=HTTP_REQUEST_NODE_SSL_VERIFY
  32. ),
  33. }
  34. return httpx.Client(
  35. verify=HTTP_REQUEST_NODE_SSL_VERIFY,
  36. headers=headers or {},
  37. timeout=timeout,
  38. follow_redirects=True,
  39. mounts=proxy_mounts,
  40. )
  41. else:
  42. return httpx.Client(
  43. verify=HTTP_REQUEST_NODE_SSL_VERIFY,
  44. headers=headers or {},
  45. timeout=timeout,
  46. follow_redirects=True,
  47. )
  48. def ssrf_proxy_sse_connect(url, **kwargs):
  49. """Connect to SSE endpoint with SSRF proxy protection.
  50. This function creates an SSE connection using the configured proxy settings
  51. to prevent SSRF attacks when connecting to external endpoints.
  52. Args:
  53. url: The SSE endpoint URL
  54. **kwargs: Additional arguments passed to the SSE connection
  55. Returns:
  56. EventSource object for SSE streaming
  57. """
  58. from httpx_sse import connect_sse
  59. # Extract client if provided, otherwise create one
  60. client = kwargs.pop("client", None)
  61. if client is None:
  62. # Create client with SSRF proxy configuration
  63. timeout = kwargs.pop(
  64. "timeout",
  65. httpx.Timeout(
  66. timeout=dify_config.SSRF_DEFAULT_TIME_OUT,
  67. connect=dify_config.SSRF_DEFAULT_CONNECT_TIME_OUT,
  68. read=dify_config.SSRF_DEFAULT_READ_TIME_OUT,
  69. write=dify_config.SSRF_DEFAULT_WRITE_TIME_OUT,
  70. ),
  71. )
  72. headers = kwargs.pop("headers", {})
  73. client = create_ssrf_proxy_mcp_http_client(headers=headers, timeout=timeout)
  74. client_provided = False
  75. else:
  76. client_provided = True
  77. # Extract method if provided, default to GET
  78. method = kwargs.pop("method", "GET")
  79. try:
  80. return connect_sse(client, method, url, **kwargs)
  81. except Exception:
  82. # If we created the client, we need to clean it up on error
  83. if not client_provided:
  84. client.close()
  85. raise
  86. def create_mcp_error_response(request_id: int | str | None, code: int, message: str, data=None):
  87. """Create MCP error response"""
  88. error_data = ErrorData(code=code, message=message, data=data)
  89. json_response = JSONRPCError(
  90. jsonrpc="2.0",
  91. id=request_id or 1,
  92. error=error_data,
  93. )
  94. json_data = json.dumps(jsonable_encoder(json_response))
  95. sse_content = f"event: message\ndata: {json_data}\n\n".encode()
  96. yield sse_content