# How the McpClients Factory Determines Transport Implementation in Dify MCP

> Discover how the McpClients factory selects transport implementations in Dify MCP by examining server configurations. Learn about default SSE and Streamable HTTP choices.

- Repository: [Junjie.M/dify-plugin-tools-mcp_sse](https://github.com/junjiem/dify-plugin-tools-mcp_sse)
- Tags: internals
- Published: 2026-03-05

---

**The `McpClients` factory determines which transport implementation to instantiate by inspecting the `transport` field in each server's configuration, defaulting to `McpSseClient` when the value is `"sse"` or undefined, and instantiating `McpStreamableHttpClient` only when the configuration explicitly specifies `"streamable_http"`.**

The `junjiem/dify-plugin-tools-mcp_sse` repository implements a factory pattern that abstracts transport-specific details for MCP (Model Context Protocol) connections. This design allows Dify plugins to communicate with MCP servers through either Server-Sent Events (SSE) or HTTP transports without changing the underlying business logic. The factory centralizes transport selection logic in a single configuration-driven method, making it trivial to switch protocols by editing YAML configuration rather than code.

## Transport Selection Logic

The factory's decision process follows a strict validation and branching sequence defined in [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py). Each server entry in the configuration undergoes name validation before transport selection occurs.

### Configuration Schema and Defaults

Every server configuration must include a `transport` field that dictates which concrete client class the factory instantiates. If the field is omitted, the factory falls back to the string value `"sse"`. The configuration structure follows this pattern:

- **Server name**: Must match the regex pattern `^[a-zA-Z0-9_-]+$` (alphanumeric, underscores, and hyphens only)
- **Transport value**: Either `"sse"` (default), `"streamable_http"`, or potentially other custom values
- **URL and headers**: Transport-specific connection parameters

### The Decision Branch in init_client

The static method `McpClients.init_client` (lines 44-66 in [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py)) implements the selection logic through a simple conditional branch:

1. **Extract transport**: Read the `transport` field from the server configuration dictionary
2. **Compare value**: Check if the string equals `"streamable_http"`
3. **Instantiate**: Create `McpStreamableHttpClient` for HTTP transport, or `McpSseClient` for any other value including the default `"sse"`

The chosen client is automatically initialized via `client.initialize()` and stored in the factory's internal `_clients` dictionary for later retrieval.

## Factory Implementation Details

Understanding the internal mechanics of `McpClients` reveals how the plugin maintains clean separation between transport protocols while providing a unified interface.

### Client Instantiation Flow

When `McpClients` receives its configuration (typically from Dify's settings or a YAML file), it iterates through the `mcpServers` dictionary and calls `init_client` for each entry. The method validates required parameters before instantiation:

```python
@staticmethod
def init_client(server_name: str, server_config: dict):
    # Validation: server_name must match ^[a-zA-Z0-9_-]+$

    # Extraction: transport = server_config.get("transport", "sse")

    # Branching: if transport == "streamable_http" → McpStreamableHttpClient

    #           else → McpSseClient

```

After instantiation, the factory immediately invokes `initialize()` on the new client, establishing the connection to the MCP server before the client enters the internal registry.

### Storage and Retrieval

The factory maintains a private dictionary `_clients` that maps server names to their initialized transport instances. This allows the plugin tools ([`mcp_list_tools.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/mcp_list_tools.py), [`mcp_call_tool.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/mcp_call_tool.py)) to retrieve clients by string name without knowing the underlying transport type:

```python

# Accessing stored clients

sse_client = clients._clients["my_sse_server"]      # McpSseClient instance

http_client = clients._clients["my_http_server"]    # McpStreamableHttpClient instance

```

## Configuration Examples

Practical deployment scenarios demonstrate how the factory interprets different configuration values to instantiate the correct transport implementation.

### Default SSE Transport

When the `transport` field is omitted, the factory defaults to SSE. This configuration creates an `McpSseClient` instance:

```python
servers_config = {
    "mcpServers": {
        "filesystem_server": {
            "url": "http://localhost:3000/mcp/sse",
            # transport not specified → defaults to "sse"

        }
    }
}

clients = McpClients(servers_config)

# Results in _clients["filesystem_server"] = McpSseClient(...)

```

### Explicit Streamable HTTP

To instantiate `McpStreamableHttpClient`, the configuration must explicitly declare the transport type. This is required for HTTP-based MCP servers:

```python
servers_config = {
    "mcpServers": {
        "remote_api_server": {
            "url": "https://api.example.com/mcp",
            "transport": "streamable_http",
            "headers": {
                "Authorization": "Bearer <token>",
                "Content-Type": "application/json"
            }
        }
    }
}

clients = McpClients(servers_config)

# Results in _clients["remote_api_server"] = McpStreamableHttpClient(...)

```

## Extending the Factory Pattern

The centralized transport selection in `init_client` makes the codebase extensible. Adding support for a hypothetical WebSocket transport would require only a new conditional branch:

```python
if transport == "streamable_http":
    return McpStreamableHttpClient(server_name, server_config)
elif transport == "websocket":
    return McpWebSocketClient(server_name, server_config)
else:
    return McpSseClient(server_name, server_config)  # default

```

Because `McpClient` serves as an abstract base class, new transport implementations only need to satisfy the interface contract. The factory would automatically route configuration requests to the new class without modifying calling code in [`tools/mcp_list_tools.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/tools/mcp_list_tools.py) or [`tools/mcp_call_tool.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/tools/mcp_call_tool.py).

## Summary

- **The `McpClients` factory** determines transport implementation by reading the `transport` configuration field and choosing between `McpSseClient` and `McpStreamableHttpClient`.
- **Default behavior** routes all undefined or `"sse"` values to the SSE transport, while `"streamable_http"` triggers the HTTP client.
- **Validation occurs** before instantiation: server names must match `^[a-zA-Z0-9_-]+$` according to the logic in [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py) lines 44-66.
- **Automatic initialization** happens immediately after instantiation via `client.initialize()`, storing the result in the `_clients` registry.
- **Extension requires** only adding new conditional branches to `init_client`, following the existing pattern in `junjiem/dify-plugin-tools-mcp_sse`.

## Frequently Asked Questions

### What happens if I specify an unsupported transport name in the configuration?

The factory treats any unknown transport value as the default SSE implementation. If you specify `"websocket"` or `"grpc"` without modifying [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py), the factory will instantiate `McpSseClient` rather than raising an error. This fallback behavior ensures backward compatibility but requires explicit implementation for new protocols.

### Where is the transport selection logic located in the source code?

According to the `junjiem/dify-plugin-tools-mcp_sse` repository, the transport selection logic resides in [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py) between lines 44 and 66. The static method `init_client` contains the conditional branching that compares the configuration's `transport` field against the string `"streamable_http"` to determine which concrete class to instantiate.

### Can I use both SSE and Streamable HTTP transports in the same Dify plugin instance?

Yes. The factory supports heterogeneous transport configurations within a single `McpClients` instance. You can define multiple servers in the `mcpServers` configuration object, with some using `"sse"` (or omitting the field) and others explicitly using `"streamable_http"`. The factory creates the appropriate client type for each server and stores them in the `_clients` dictionary under their respective server names.

### How does the factory handle client initialization errors?

The factory invokes `client.initialize()` immediately after instantiation within the `init_client` method. If the initialization fails (e.g., connection refused, authentication failure), the exception propagates up to the caller because there is no try-catch block in the standard implementation. This ensures that configuration errors surface immediately during plugin startup rather than during tool execution.