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 mcpServers key 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: Uses McpStreamableHttpClient for HTTP-based streaming
  • sse (default): Uses McpSseClient for 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_config credential as a JSON map where each key represents a distinct MCP server.
  • McpClients in utils/mcp_client.py handles both direct configuration objects and those wrapped in an mcpServers key.
  • Each server entry spawns either an McpSseClient or McpStreamableHttpClient based on the transport field.
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →