How to Configure Multiple MCP Servers in a Single Credentials JSON for Dify
The Dify MCP SSE plugin supports multiple MCP servers by parsing the servers_config credential field as a JSON map, creating a dedicated client for each entry, and automatically merging their toolsets with collision handling.
The junjiem/dify-plugin-tools-mcp_sse repository enables Dify agents to connect to Model Context Protocol (MCP) servers using Server-Sent Events (SSE) and HTTP streams. When configuring the plugin, you can define configuring multiple MCP servers in a single credentials JSON to avoid managing separate credential entries for each endpoint.
Understanding the servers_config Structure
The plugin expects a JSON object stored in the servers_config field of the provider credentials. This object maps server identifiers to their connection parameters. Each key becomes the server name, and each value defines the transport protocol, URL, headers, and timeouts.
The JSON supports two top-level formats:
- Direct map: An object where keys are server names and values are configuration objects
- Wrapped map: An object containing a single
mcpServerskey that holds the server map
How the Plugin Parses Multiple Server Configurations
Credential Validation in provider/mcp_tool.py
When you save credentials in Dify, the McpToolProvider._validate_credentials method in provider/mcp_tool.py processes the input. It extracts the servers_config string, validates it as JSON, and instantiates the client manager.
# provider/mcp_tool.py
servers_config_json = credentials.get("servers_config", "")
if not servers_config_json:
raise ToolProviderCredentialValidationError("Please fill in the servers_config")
servers_config = json.loads(servers_config_json) # ← parses the whole map
mcp_clients = McpClients(servers_config) # ← creates a client per entry
mcp_clients.fetch_tools() # ← ensures every server is reachable
Handling the mcpServers Wrapper
The McpClients class in utils/mcp_client.py normalizes the configuration structure. If it detects the wrapper format, it unwraps the configuration before processing individual servers.
# utils/mcp_client.py
if "mcpServers" in servers_config:
servers_config = servers_config["mcpServers"]
Creating Clients for Each MCP Server
Transport Protocol Selection
For each entry in the configuration map, McpClients.init_client creates a concrete client instance based on the transport field. The plugin supports two transport mechanisms:
streamable_http: UsesMcpStreamableHttpClientfor HTTP-based streamingsse(default): UsesMcpSseClientfor Server-Sent Events connections
# utils/mcp_client.py
def init_client(name: str, config: dict[str, Any]) -> McpClient:
transport = config.get("transport", "sse")
if transport == "streamable_http":
return McpStreamableHttpClient(
name=name,
url=config.get("url"),
headers=config.get("headers"),
timeout=config.get("timeout", 50),
)
return McpSseClient(
name=name,
url=config.get("url"),
headers=config.get("headers"),
timeout=config.get("timeout", 50),
sse_read_timeout=config.get("sse_read_timeout", 50),
)
Server Name Validation
Before creating the client, the plugin validates the server name against the regex ^[a-zA-Z0-9_-]+$ to ensure it contains only alphanumeric characters, underscores, and hyphens. This prevents illegal identifiers from breaking the tool namespacing logic.
Aggregating Tools from Multiple Servers
Automatic Namespacing for Collisions
After initializing all clients, McpClients.fetch_tools() retrieves available tools from every connected server. When tool names collide across different servers, the plugin automatically namespaces them using the pattern {server_name}__{tool_name}.
# utils/mcp_client.py (inside fetch_tools)
for tool in tools:
name = tool["name"]
if name in self._tool_actions: # already seen on another server
name = f"{server_name}__{name}" # prefix with server identifier
self._tool_actions[name] = ToolAction(...)
This ensures that tools from serverA and serverB remain distinct even if both expose a tool named read_file.
Practical Configuration Examples
Here is a complete example of configuring three different MCP servers in a single credentials JSON:
{
"filesystem": {
"transport": "sse",
"url": "http://127.0.0.1:8000/sse",
"headers": {},
"timeout": 50,
"sse_read_timeout": 50
},
"github": {
"transport": "streamable_http",
"url": "http://127.0.0.1:8001/mcp",
"headers": {
"Authorization": "Bearer token123"
}
},
"brave_search": {
"url": "https://router.mcp.so/sse/xxxxxxxx",
"timeout": 30
}
}
When pasted into the Dify plugin's servers_config field, this JSON creates three separate client instances. The filesystem and brave_search servers use SSE transport, while github uses the streamable HTTP protocol.
Summary
- The plugin parses the
servers_configcredential as a JSON map where each key represents a distinct MCP server. McpClientsinutils/mcp_client.pyhandles both direct configuration objects and those wrapped in anmcpServerskey.- Each server entry spawns either an
McpSseClientorMcpStreamableHttpClientbased on thetransportfield. - Server names must match
^[a-zA-Z0-9_-]+$to ensure valid identifier generation. - Tool collisions are resolved automatically by prefixing duplicate names with their server identifier using the
__delimiter.
Frequently Asked Questions
What happens if two MCP servers expose tools with identical names?
The plugin automatically namespaces colliding tools by prefixing the tool name with the server name and a double underscore. For example, if both serverA and serverB have a tool named read_file, they become serverA__read_file and serverB__read_file in the Dify tool list.
Can I mix SSE and HTTP transport protocols in the same credentials JSON?
Yes. The transport field within each server configuration determines the protocol for that specific endpoint. You can define one server with "transport": "sse", another with "transport": "streamable_http", and omit the field entirely for others (defaulting to SSE), all within the same servers_config JSON object.
What is the purpose of the mcpServers wrapper key?
The mcpServers wrapper provides compatibility with configuration formats used by other MCP ecosystems. If the top-level JSON object contains an mcpServers key, the plugin extracts the value of that key as the actual server configuration map. This allows you to paste configurations directly from other MCP management tools without restructuring the JSON.
How does the plugin validate server names before creating clients?
Before instantiating a client for each server entry, the plugin validates the server name against the regular expression ^[a-zA-Z0-9_-]+$ in utils/mcp_client.py. This ensures names contain only alphanumeric characters, underscores, and hyphens, preventing illegal identifiers from breaking the tool namespacing logic or causing runtime errors during tool invocation.
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 →