Summary
MCPProvider only supports SSE (Server-Sent Events) transport for remote MCP servers. The MCP ecosystem is moving toward Streamable HTTP as the standard remote transport, and many popular MCP servers (e.g. Tavily's remote endpoint) already use it exclusively. Connecting to these servers with the current MCPProvider silently hangs until the health-check timeout (up to 5 minutes), with no indication that the transport is wrong.
Problem
The MCPProvider config class hardcodes provider_type: Literal["sse"], and MCPIOService._get_or_create_session() unconditionally uses sse_client() for all non-stdio providers:
# engine/mcp/io.py, line 214-216
else:
headers = _build_auth_headers(provider.api_key)
ctx = sse_client(provider.endpoint, headers=headers)
When a user points this at a Streamable HTTP endpoint (e.g. https://mcp.tavily.com/mcp/), the SSE client opens a connection expecting an SSE event stream, receives nothing, and blocks until the full timeout_sec elapses (default 300s). The resulting MCPToolError gives no hint that the transport mismatch is the root cause.
Observed behavior:
[12:00:22] [INFO] 🧰 Running health checks for MCP tools...
[12:00:22] [INFO] |-- 👀 Checking tools for tool alias 'tavily'...
[12:05:22] [ERROR] |-- ❌ Failed!
MCPToolError: Timed out after 300.0s while listing tools on 'tavily'.
The mcp Python SDK (v1.26.0, already a dependency) ships mcp.client.streamable_http — it just isn't wired up.
Proposed Changes
1. Add Streamable HTTP transport to MCPProvider
Either generalize the existing MCPProvider or add a new provider type:
Option A — Extend MCPProvider (minimal change):
class MCPProvider(ConfigBase):
provider_type: Literal["sse", "streamable_http"] = "sse"
name: str
endpoint: str
api_key: str | None = None
Option B — New config class (preserves backward compat):
class StreamableHTTPMCPProvider(ConfigBase):
provider_type: Literal["streamable_http"] = "streamable_http"
name: str
endpoint: str
api_key: str | None = None
Then update the discriminated union:
MCPProviderT = Annotated[
MCPProvider | StreamableHTTPMCPProvider | LocalStdioMCPProvider,
Field(discriminator="provider_type"),
]
2. Wire up streamable_http_client in io.py
Add a branch in _get_or_create_session():
from mcp.client.streamable_http import streamablehttp_client
if isinstance(provider, LocalStdioMCPProvider):
ctx = stdio_client(params)
elif provider.provider_type == "streamable_http":
headers = _build_auth_headers(provider.api_key)
ctx = streamablehttp_client(provider.endpoint, headers=headers)
else:
headers = _build_auth_headers(provider.api_key)
ctx = sse_client(provider.endpoint, headers=headers)
3. (Nice-to-have) Auto-negotiate transport with fallback
Try Streamable HTTP first, fall back to SSE if the handshake fails. This would let users write MCPProvider(endpoint=...) without needing to know which transport the server uses:
async def _connect_remote(self, provider):
headers = _build_auth_headers(provider.api_key)
try:
ctx = streamablehttp_client(provider.endpoint, headers=headers)
read, write = await ctx.__aenter__()
return ctx, read, write
except Exception:
logger.info("Streamable HTTP failed for %r, falling back to SSE", provider.name)
ctx = sse_client(provider.endpoint, headers=headers)
read, write = await ctx.__aenter__()
return ctx, read, write
4. Shorter health-check timeout for list_tools
The 5-minute hang on a wrong transport is painful. A dedicated health_check_timeout_sec (defaulting to ~30s) on the provider or tool config would catch misconfigurations quickly, independent of the timeout_sec used for actual tool calls during generation.
Environment
data-designer: 0.5.1
mcp SDK: 1.26.0 (already includes mcp.client.streamable_http)
- Tested against: Tavily remote MCP (
https://mcp.tavily.com/mcp/)
Workaround
Use LocalStdioMCPProvider with npx -y tavily-mcp@latest (or a custom Python MCP server) instead of the remote endpoint.
Summary
MCPProvideronly supports SSE (Server-Sent Events) transport for remote MCP servers. The MCP ecosystem is moving toward Streamable HTTP as the standard remote transport, and many popular MCP servers (e.g. Tavily's remote endpoint) already use it exclusively. Connecting to these servers with the currentMCPProvidersilently hangs until the health-check timeout (up to 5 minutes), with no indication that the transport is wrong.Problem
The
MCPProviderconfig class hardcodesprovider_type: Literal["sse"], andMCPIOService._get_or_create_session()unconditionally usessse_client()for all non-stdio providers:When a user points this at a Streamable HTTP endpoint (e.g.
https://mcp.tavily.com/mcp/), the SSE client opens a connection expecting an SSE event stream, receives nothing, and blocks until the fulltimeout_secelapses (default 300s). The resultingMCPToolErrorgives no hint that the transport mismatch is the root cause.Observed behavior:
The
mcpPython SDK (v1.26.0, already a dependency) shipsmcp.client.streamable_http— it just isn't wired up.Proposed Changes
1. Add Streamable HTTP transport to
MCPProviderEither generalize the existing
MCPProvideror add a new provider type:Option A — Extend
MCPProvider(minimal change):Option B — New config class (preserves backward compat):
Then update the discriminated union:
2. Wire up
streamable_http_clientinio.pyAdd a branch in
_get_or_create_session():3. (Nice-to-have) Auto-negotiate transport with fallback
Try Streamable HTTP first, fall back to SSE if the handshake fails. This would let users write
MCPProvider(endpoint=...)without needing to know which transport the server uses:4. Shorter health-check timeout for
list_toolsThe 5-minute hang on a wrong transport is painful. A dedicated
health_check_timeout_sec(defaulting to ~30s) on the provider or tool config would catch misconfigurations quickly, independent of thetimeout_secused for actual tool calls during generation.Environment
data-designer: 0.5.1mcpSDK: 1.26.0 (already includesmcp.client.streamable_http)https://mcp.tavily.com/mcp/)Workaround
Use
LocalStdioMCPProviderwithnpx -y tavily-mcp@latest(or a custom Python MCP server) instead of the remote endpoint.