MCP Client Transport and Connectivity#
MCPClient (api/core/mcp/mcp_client.py) is the entry point for all outbound MCP server communication in Dify. It supports two transport backends β SSE (via sse_client) and Streamable-HTTP (via streamablehttp_client) β and uses a contextlib.ExitStack to manage their lifetimes .
Transport Selection and Fallback#
Protocol selection is performed in _initialize() using a two-step strategy:
-
URL-path detection. The last segment of the server URL path is checked against a
connection_methodsdict :- Path ends in
/mcpβstreamablehttp_client - Path ends in
/sseβsse_client
- Path ends in
-
Automatic fallback. If the path segment doesn't match either key, Dify first attempts SSE; if that raises
MCPConnectionErrororValueError, it falls back to Streamable-HTTP :try sse_client β except (MCPConnectionError, ValueError) β streamablehttp_clientThe
ValueErrorcatch was added in PR #29960 to handle transport initialization failures that surface before a connection-level error is raised. The earlier PR #22645 refactored the fallback from a broad try-except block into the explicit path-lookup + fallback structure now in place.
Once a transport is selected, connect_server() enters the transport context via ExitStack, then initializes a ClientSession and calls session.initialize().
Note on streamable-HTTP stream unpacking: The streamable client returns a 3-tuple (read_stream, write_stream, _); the SSE client returns a 2-tuple (read_stream, write_stream). connect_server() handles both cases .
Well-Known URL Discovery (RFC 9728)#
PR #29960 added RFC 9728βcompliant OAuth protected resource metadata discovery. When validating or reconnecting to an MCP provider, Dify tries multiple well-known URLs in priority order:
- URL from the
WWW-Authenticateresponse header (if present) /.well-known/oauth-protected-resource{path}(path insertion, for path-based multi-tenant deployments)/.well-known/oauth-protected-resource(root fallback)
A parallel expansion was made for OAuth authorization server metadata to support both RFC 8414 and RFC 9728 variants (OIDC path-appending, OAuth 2.0 path insertion, and root endpoints).
Timeout Configuration#
Two timeout parameters are threaded through the entire stack from API request to HTTP transport (added in PR #23546):
| Parameter | Purpose | Default (DB) |
|---|---|---|
timeout | Connection / general HTTP request timeout | 30 s |
sse_read_timeout | SSE stream read timeout | 300 s |
These values are persisted per-provider in the MCPToolProvider database model (columns timeout FLOAT DEFAULT 30, sse_read_timeout FLOAT DEFAULT 300), configurable via the tool provider API endpoints.
The two transport implementations have different internal defaults when no explicit value is passed:
| Transport | timeout default | sse_read_timeout default |
|---|---|---|
sse_client | 5 s | 60 s |
streamablehttp_client | 30 s | 300 s (5 min) |
The streamablehttp_client also accepts timedelta objects, converting them to seconds internally.
Config flow:
API controller β MCPToolManageService β MCPToolProvider (DB) β MCPTool β MCPClient β transport
MCPClient.__init__ accepts timeout and sse_read_timeout directly and passes them to the transport factory in connect_server() .
Header Forwarding#
MCPClient supports forwarding incoming request headers to the MCP server. Header values may contain {{ request.headers.<name> }} placeholders, which are substituted at construction time when a Flask request context is active .
Key Source Files#
| File | Purpose |
|---|---|
api/core/mcp/mcp_client.py | Main MCPClient class, transport selection, fallback |
api/core/mcp/client/sse_client.py | SSE transport implementation |
api/core/mcp/client/streamable_client.py | Streamable-HTTP transport implementation |
api/models/tools.py | MCPToolProvider DB model β persists timeout config |