How to Configure Multiple MCP Servers with Different Transport Types (SSE and Streamable HTTP) in Dify
You can configure multiple MCP servers with mixed SSE and Streamable HTTP transports by defining each server in the mcpServers JSON configuration with a transport field set to either "sse" or "streamable_http", and the McpClients class will automatically instantiate the correct client type for each.
The junjiem/dify-plugin-agent-mcp_sse repository provides a Dify plugin that enables agents to interact with multiple Model Context Protocol (MCP) servers simultaneously. When you configure multiple MCP servers with different transport types, the plugin's McpClients manager handles the complexity of routing requests to the appropriate transport layer without requiring additional code changes.
Understanding MCP Transport Types
The plugin supports two distinct transport mechanisms for communicating with MCP servers. Understanding these differences helps you choose the right configuration for your infrastructure.
Server-Sent Events (SSE)
SSE establishes a persistent connection where the server streams events to the client over HTTP. In utils/mcp_client.py, the McpSseClient class handles this transport by opening an SSE connection, waiting for an endpoint event to receive the session URL, and then sending JSON-RPC messages over that endpoint. This approach is ideal for real-time, bidirectional communication where the server needs to push updates to the agent.
Streamable HTTP
Streamable HTTP uses standard HTTP POST requests for each interaction, with responses returned as either JSON payloads or text/event-streams. The McpStreamableHttpClient class in utils/mcp_client.py implements this by sending discrete POST requests per tool invocation. This transport is more firewall-friendly and works better in serverless or ephemeral environments where persistent connections are difficult to maintain.
Configuration Structure for Multiple MCP Servers
The plugin uses a JSON-based configuration system that allows you to define heterogeneous transport types within the same configuration object.
The mcpServers Configuration Object
In utils/mcp_client.py, the McpClients.__init__ method (lines 28-38) expects a configuration dictionary containing a top-level key mcpServers. Each key within this object represents a unique server name, and the value is a configuration object that must include:
transport: Either"sse"or"streamable_http"(defaults to"sse"if omitted)url: The base URL for the MCP server endpointheaders: Optional HTTP headers for authenticationtimeout: Request timeout in secondssse_read_timeout: SSE-specific read timeout (SSE transport only)
Transport Selection Logic
The static method McpClients.init_client (lines 49-68 in utils/mcp_client.py) examines the transport field for each server configuration:
# Simplified logic from utils/mcp_client.py
if server_config.get("transport") == "streamable_http":
client = McpStreamableHttpClient(server_config)
else:
client = McpSseClient(server_config) # Default fallback
This automatic detection means you can mix both transport types in the same configuration without writing conditional logic.
Step-by-Step Configuration Guide
Follow these steps to configure multiple MCP servers with different transport types in your Dify plugin instance.
Step 1: Create the Configuration JSON
Construct a JSON object that defines your servers under the mcpServers key. Ensure each server specifies the correct transport type:
{
"mcpServers": {
"analytics_sse": {
"transport": "sse",
"url": "http://127.0.0.1:8000/sse",
"headers": {
"Authorization": "Bearer token123"
},
"timeout": 50,
"sse_read_timeout": 50
},
"search_http": {
"transport": "streamable_http",
"url": "http://127.0.0.1:8002/mcp",
"headers": {
"X-API-Key": "secret"
},
"timeout": 30
}
}
}
Step 2: Initialize the Client Manager
Pass the configuration to the McpClients constructor. The manager automatically instantiates the appropriate client type for each server:
import json
from utils.mcp_client import McpClients
# Load your configuration
config = json.loads(config_json)
# Initialize clients - this creates both McpSseClient and McpStreamableHttpClient instances
clients = McpClients(
config,
resources_as_tools=True,
prompts_as_tools=True
)
Step 3: Verify Tool Discovery
Confirm that tools from both servers are available. The fetch_tools() method aggregates capabilities from all configured servers:
# Retrieve all available tools from both SSE and HTTP servers
tools = clients.fetch_tools()
for tool in tools:
# Each tool action contains metadata about its originating server
action = clients._tool_actions[tool['name']]
print(f"Tool: {tool['name']} | Server: {action.server_name} | Transport: {action.transport_type}")
Step 4: Execute Tools Across Transports
Invoke tools without worrying about the underlying transport. The execute_tool method routes requests to the correct client:
# Execute a tool from the SSE server
result_sse = clients.execute_tool(
"analytics_sse__get_metrics",
{"metric_type": "cpu_usage"}
)
# Execute a tool from the Streamable HTTP server
result_http = clients.execute_tool(
"search_http__web_search",
{"query": "machine learning"}
)
Code Implementation Details
Understanding the internal mechanics helps troubleshoot configuration issues.
Client Initialization Logic
In utils/mcp_client.py, the McpClients class maintains a dictionary _clients that maps server names to transport-specific instances. The init_client static method acts as a factory:
# From utils/mcp_client.py (lines 49-68)
@staticmethod
def init_client(name: str, server_config: dict) -> McpClient:
transport = server_config.get("transport", "sse")
if transport == "streamable_http":
return McpStreamableHttpClient(server_config)
else:
return McpSseClient(server_config)
Transport-Specific Implementations
McpSseClient (lines 81-106 in utils/mcp_client.py) handles the SSE protocol by maintaining a persistent connection and parsing endpoint events to establish the JSON-RPC message channel.
McpStreamableHttpClient (lines 32-86 in utils/mcp_client.py) implements stateless HTTP POST requests, checking the Content-Type header to determine whether the response is a direct JSON payload or an event stream requiring parsing.
Summary
- Mixed transport support: The
junjiem/dify-plugin-agent-mcp_sseplugin supports simultaneous connections to both SSE and Streamable HTTP MCP servers through a unified configuration interface. - Automatic client selection: The
McpClientsclass inutils/mcp_client.pyautomatically instantiatesMcpSseClientorMcpStreamableHttpClientbased on thetransportfield in each server configuration. - Configuration key: Use the
mcpServersJSON object to define multiple servers, specifying"transport": "sse"or"transport": "streamable_http"for each. - Unified execution: Once configured, tools from all servers appear in a single aggregated list, and
execute_toolroutes requests to the correct transport transparently.
Frequently Asked Questions
Can I mix SSE and Streamable HTTP servers in the same configuration file?
Yes. The mcpServers configuration object accepts any combination of transport types. Each server entry is processed independently by McpClients.init_client, which checks the transport field and creates the appropriate client instance—McpSseClient for SSE connections or McpStreamableHttpClient for HTTP-based servers.
What happens if I omit the transport field in a server configuration?
If the transport field is missing, the system defaults to SSE. In utils/mcp_client.py, the init_client method uses server_config.get("transport", "sse"), which falls back to the string "sse" when no transport is specified, resulting in an McpSseClient being instantiated.
Do I need different timeout settings for SSE versus Streamable HTTP?
SSE connections typically require longer timeouts because they maintain persistent connections. The SSE client supports an additional sse_read_timeout parameter specifically for the long-lived SSE connection, while both transports accept a general timeout parameter for HTTP request/response cycles. Streamable HTTP servers usually work with standard timeout values since each request is independent.
How does the plugin route tool execution to the correct transport?
The McpClients class maintains an internal dictionary _clients that maps server names to their respective transport clients, and a _tool_actions dictionary that records which server owns each tool. When execute_tool is called, it looks up the tool name in _tool_actions to identify the originating server, retrieves the appropriate client from _clients, and delegates the request to that client's call_tool method—whether it's an SSE or HTTP implementation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →