Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions backend/services/remote_mcp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
import asyncio
import socket
import random

from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport, SSETransport

from consts.const import CAN_EDIT_ALL_USER_ROLES, PERMISSION_EDIT, PERMISSION_READ, NEXENT_MCP_DOCKER_IMAGE
from consts.exceptions import (
MCPConnectionError,
Expand Down Expand Up @@ -38,7 +36,7 @@
)
from database.user_tenant_db import get_user_tenant_by_user_id
from services.mcp_container_service import MCPContainerManager
from services.tool_configuration_service import get_tool_from_remote_mcp_server
from utils.http_client_utils import create_httpx_client

logger = logging.getLogger("remote_mcp_service")

Expand All @@ -58,11 +56,24 @@ async def mcp_server_health(remote_mcp_server: str, authorization_token: str | N
headers.update(custom_headers)

if url_stripped.endswith("/sse"):
transport = SSETransport(url=url_stripped, headers=headers)
transport = SSETransport(
url=url_stripped,
headers=headers,
httpx_client_factory=create_httpx_client
)
elif url_stripped.endswith("/mcp"):
transport = StreamableHttpTransport(url=url_stripped, headers=headers)
transport = StreamableHttpTransport(
url=url_stripped,
headers=headers,
httpx_client_factory=create_httpx_client
)
else:
transport = StreamableHttpTransport(url=url_stripped, headers=headers)
# Default to StreamableHttpTransport for unrecognized formats
transport = StreamableHttpTransport(
url=url_stripped,
headers=headers,
httpx_client_factory=create_httpx_client
)

client = Client(transport=transport)
async with client:
Expand Down Expand Up @@ -1024,6 +1035,7 @@ async def list_mcp_service_tools_by_id(*, tenant_id: str, mcp_id: int) -> list[d
authorization_token = record.get("authorization_token")
custom_headers = record.get("custom_headers")

from services.tool_configuration_service import get_tool_from_remote_mcp_server
tools_info = await get_tool_from_remote_mcp_server(
mcp_server_name=service_name,
remote_mcp_server=server_url,
Expand Down
7 changes: 4 additions & 3 deletions backend/services/tool_configuration_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from mcpadapt.smolagents_adapter import _sanitize_function_name
from services.file_management_service import get_llm_model, validate_urls_access
from services.vectordatabase_service import get_embedding_model_by_index_name, get_rerank_model
from utils.http_client_utils import create_httpx_client
from database.client import minio_client
from services.image_service import get_video_understanding_model, get_vlm_model
from nexent.monitor import set_monitoring_context, set_monitoring_operation
Expand Down Expand Up @@ -68,12 +69,12 @@ def _create_mcp_transport(url: str, authorization_token: Optional[str] = None, c
headers.update(custom_headers)

if url_stripped.endswith("/sse"):
return SSETransport(url=url_stripped, headers=headers)
return SSETransport(url=url_stripped, headers=headers, httpx_client_factory=create_httpx_client)
elif url_stripped.endswith("/mcp"):
return StreamableHttpTransport(url=url_stripped, headers=headers)
return StreamableHttpTransport(url=url_stripped, headers=headers, httpx_client_factory=create_httpx_client)
else:
# Default to StreamableHttpTransport for unrecognized formats
return StreamableHttpTransport(url=url_stripped, headers=headers)
return StreamableHttpTransport(url=url_stripped, headers=headers, httpx_client_factory=create_httpx_client)


def python_type_to_json_schema(annotation: Any) -> str:
Expand Down
18 changes: 18 additions & 0 deletions backend/utils/http_client_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""HTTP client factory utilities shared across services."""

import httpx
from httpx import AsyncClient


def create_httpx_client(
headers: dict[str, str] | None = None,
timeout: httpx.Timeout | None = None,
auth: httpx.Auth | None = None,
) -> AsyncClient:
return AsyncClient(
headers=headers,
timeout=timeout,
auth=auth,
trust_env=False,
verify=False,

Check failure on line 17 in backend/utils/http_client_utils.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Enable server certificate validation on this SSL/TLS connection.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ53uNSG47GWV_L6YCVP&open=AZ53uNSG47GWV_L6YCVP&pullRequest=3121
)
4 changes: 2 additions & 2 deletions test/backend/services/test_remote_mcp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ async def test_health_without_custom_headers(self, mock_get, mock_health, mock_s
class TestListMcpServiceToolsByIdCustomHeaders(unittest.IsolatedAsyncioTestCase):
"""Test list_mcp_service_tools_by_id uses custom_headers from record."""

@patch('backend.services.remote_mcp_service.get_tool_from_remote_mcp_server')
@patch('backend.services.tool_configuration_service.get_tool_from_remote_mcp_server')
@patch('backend.services.remote_mcp_service.get_mcp_record_by_id_and_tenant')
async def test_tools_with_custom_headers(self, mock_get, mock_get_tools):
"""Test list_mcp_service_tools_by_id passes custom_headers to tool retrieval."""
Expand All @@ -800,7 +800,7 @@ async def test_tools_with_custom_headers(self, mock_get, mock_get_tools):
custom_headers={"X-Tools-Custom": "tools-value"},
)

@patch('backend.services.remote_mcp_service.get_tool_from_remote_mcp_server')
@patch('backend.services.tool_configuration_service.get_tool_from_remote_mcp_server')
@patch('backend.services.remote_mcp_service.get_mcp_record_by_id_and_tenant')
async def test_tools_without_custom_headers(self, mock_get, mock_get_tools):
"""Test list_mcp_service_tools_by_id when custom_headers is None."""
Expand Down
Loading