# DeepSeek-Reasonix MCP Client Implementation: Plugin System Architecture

> Explore the DeepSeek-Reasonix MCP client plugin system architecture. Discover how it integrates external tool providers via JSON-RPC 2.0 with hot-reloading and a thread-safe design.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: architecture
- Published: 2026-06-23

---

**The DeepSeek-Reasonix MCP client implements a plugin system that treats external Model Context Protocol servers as discoverable tool providers, using a JSON-RPC 2.0 transport layer, automatic namespacing with the `mcp__` prefix, and a thread-safe Host-Client architecture supporting hot-reloading.**

DeepSeek-Reasonix extends AI agent capabilities through a robust MCP (Model-Client-Plugin) client implementation that bridges external services with the core reasoning engine. This system, located in the `reasonix/internal/plugin` package, enables seamless integration of tools, prompts, and resources from any MCP-compliant server using standardized JSON-RPC communication.

## Core Architecture Components

### The Host and Client Relationship

The architecture centers on two primary types defined in [`internal/plugin/plugin.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/plugin/plugin.go): the **`Host`** and the **`Client`**.

The **Host** acts as a registry managing multiple concurrent MCP server connections. It maintains a collection of active `*Client` instances and aggregates their capabilities—tools, prompts, resources, and connection failures—behind thread-safe APIs. The Host provides methods for listing available tools, adding or removing servers dynamically, and reading resources across all connected plugins.

Each **Client** represents a single MCP server connection. It owns a specific transport implementation (stdio or HTTP) and manages the MCP protocol handshake via `initialize`, discovers available capabilities through `tools/list`, and optionally surfaces prompts and resources via `prompts/list` and `resources/list`.

### Transport Abstraction Layer

The system abstracts low-level communication through a **`transport` interface`. Concrete implementations handle protocol-specific details:

- **`newStdioTransport`** in [`internal/plugin/transport_stdio.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/plugin/transport_stdio.go) launches subprocesses using `exec.CommandContext` and manages line-delimited JSON-RPC message exchange.
- **HTTP transport** in [`internal/plugin/transport_http.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/plugin/transport_http.go) handles POST-based JSON-RPC requests and manages "auxiliary clients" for streaming prompts and resources.

Both implementations provide `call` (request/response) and `notify` (fire-and-forget) methods, with automatic session re-initialization on expired HTTP sessions.

## Connection Lifecycle and Handshake

### Startup Flow (Phase A)

The boot sequence begins with **`plugin.StartAll`** or **`StartAvailable`**, which accepts a slice of `Spec` configurations and initializes a new `Host`.

1. **Concurrency Control**: The function spawns goroutines per server spec, capped by `StartPolicy.Concurrency` to prevent resource exhaustion.
2. **Transport Initialization**: For each spec, the internal `start` helper invokes `newTransport` to instantiate the appropriate transport based on `Spec.Type` ("stdio" or "http").
3. **MCP Handshake**: The client executes `initialize` to negotiate protocol capabilities with the remote server.
4. **Tool Discovery**: The client calls `tools/list` via `Client.listTools`, parsing responses into `remoteTool` instances. Each tool receives a namespaced identifier and is registered in the global tool registry.

### Surface Discovery (Phase B)

After initial tool discovery, **`Host.StartPhaseB`** launches asynchronous discovery of optional surfaces:

- **Prompts**: Discovered via `prompts/list` and exposed to the UI as slash-commands.
- **Resources**: Discovered via `resources/list` for file-like access patterns.

Each surface streams results independently and fires `MCPSurfaceReady` events to update the UI without blocking the main agent loop.

## Tool Namespacing and Registry Integration

### The mcp__ Prefix Convention

To prevent collisions between built-in tools and external MCP tools, DeepSeek-Reasonix implements strict namespacing defined in [`internal/tool/tool.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/tool/tool.go).

The constant **`MCPNamePrefix`** defines the string `mcp__`. The utility function **`toolName(server, raw)`** constructs public tool identifiers following the pattern `mcp__<server>__<tool>`, normalizing illegal characters to ensure valid identifiers. The companion function **`SplitMCPName`** parses these identifiers to extract the server and tool components.

This namespacing ensures that a tool named `search` from a server named `codegraph` becomes `mcp__codegraph__search` in the agent's tool registry.

### Registry Integration

The **`remoteTool`** type implements the internal `Tool` interface, allowing MCP tools to integrate seamlessly with the permission layer and planner. Each `remoteTool` encapsulates:
- The namespaced tool name
- A reference to its parent `Client` for execution
- **Read-only hints**: The `ReadOnly` property mirrors the MCP `readOnlyHint` annotation, optionally overridden by `Spec.ReadOnlyToolNames` for security policy enforcement.

## Transport Implementations

### StdIO Transport

The stdio transport in [`transport_stdio.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/transport_stdio.go) manages subprocess lifecycle:

```go
// Conceptual usage of stdio transport internals
transport := newStdioTransport(ctx, command, args, env, workDir)
// Spawns: exec.CommandContext(ctx, command, args...)
// Communicates via stdin/stdout with line-delimited JSON-RPC

```

This transport is ideal for local CLI tools and language servers that expose MCP capabilities over standard streams.

### HTTP and Streamable-HTTP Transport

The HTTP transport supports remote MCP servers using POST requests with MCP-specific headers. It maintains session state and automatically re-initializes connections when receiving session-expired responses. For resource-heavy operations, the transport spawns auxiliary clients to prevent blocking the main tool execution channel.

## Runtime Management and Hot-Reloading

### Dynamic Server Management

The Host supports modifying the plugin set at runtime without restarting the agent:

- **`Host.Add`**: Hot-adds a new server using the same initialization logic as `StartAll`. It acquires the `spawningMu` lock and checks `spawnAttempt` guards to prevent duplicate connection attempts.
- **`Host.Remove`**: Gracefully closes a specific client connection and removes its tools from the registry.

### Graceful Shutdown

The **`Host.Close`** method implements ordered shutdown:
1. Cancels pending startup contexts to abort in-progress handshakes.
2. Waits for background writers to flush.
3. Closes each transport connection sequentially.

Connection failures are recorded in `Host.failures`, accessible via `Host.Failures()` for diagnostic visibility.

## Practical Implementation Examples

### Starting Configured MCP Servers at Boot

```go
ctx := context.Background()
specs := []plugin.Spec{
    {Name: "codegraph", Type: "stdio", Command: "codegraph", Args: []string{"--serve"}},
    {Name: "my-http-svc", Type: "http", URL: "https://example.com/mcp"},
}
host, tools, err := plugin.StartAll(ctx, specs)
if err != nil {
    log.Fatalf("MCP start failed: %v", err)
}
// tools contains namespaced remoteTool instances ready for the agent

```

### Hot-Adding a Server During Runtime

```go
newSpec := plugin.Spec{
    Name:    "dynamic-svc",
    Type:    "stdio",
    Command: "dynamic-svc",
    Args:    []string{"--listen"},
}
addedTools, err := host.Add(ctx, newSpec)
if err != nil {
    log.Printf("Failed to add server: %v", err)
}
for _, t := range addedTools {
    fmt.Println("New tool:", t.Name())
}

```

### Executing a Namespaced MCP Tool

```go
toolName := "mcp__codegraph__search"
t, err := tool.Registry.Get(toolName)
if err != nil {
    log.Fatal(err)
}
result, err := t.Execute(ctx, json.RawMessage(`{"query":"User"}`))
if err != nil {
    log.Fatalf("Tool error: %v", err)
}
fmt.Println("Tool output:", result)

```

### Reading MCP Resources

```go
content, err := host.ReadResource(ctx, "codegraph", "repo://myproj/main.go")
if err != nil {
    log.Fatalf("ReadResource failed: %v", err)
}
fmt.Println(content)

```

## Summary

- **Host-Client Architecture**: The `Host` in [`internal/plugin/plugin.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/plugin/plugin.go) manages multiple `Client` instances, each representing a single MCP server connection over JSON-RPC 2.0.
- **Dual Transport Support**: Implementations in [`transport_stdio.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/transport_stdio.go) and [`transport_http.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/transport_http.go) handle subprocess and HTTP-based servers respectively, with automatic session recovery.
- **Strict Namespacing**: All MCP tools receive the `mcp__<server>__` prefix defined in [`internal/tool/tool.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/tool/tool.go) to prevent collisions with built-in tools.
- **Phased Discovery**: Tools are discovered immediately during startup (Phase A), while prompts and resources load asynchronously (Phase B) via `StartPhaseB`.
- **Hot-Reloading**: The system supports runtime addition and removal of servers via `Host.Add` and `Host.Remove` with thread-safe concurrency controls.

## Frequently Asked Questions

### How does DeepSeek-Reasonix prevent tool name collisions between different MCP servers?

The system enforces automatic namespacing using the `mcp__` prefix defined in [`internal/tool/tool.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/tool/tool.go). When a server named `codegraph` exposes a tool called `search`, the Host registers it as `mcp__codegraph__search` in the global registry. The `toolName` function constructs these identifiers by normalizing illegal characters, while `SplitMCPName` parses them during execution to route calls to the correct Client instance.

### Can MCP servers be added without restarting the DeepSeek-Reasonix agent?

Yes. The `Host.Add` method in [`internal/plugin/plugin.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/plugin/plugin.go) supports hot-adding servers during active sessions. It performs the full initialization handshake—including transport creation, `initialize` negotiation, and `tools/list` discovery—while holding the `spawningMu` lock to prevent race conditions. Successfully added tools immediately become available to the planner and permission layer.

### What transport protocols does the MCP client support?

The implementation supports two primary transports: **StdIO** for local subprocess communication (implemented in [`transport_stdio.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/transport_stdio.go)) and **HTTP/Streamable-HTTP** for remote servers (implemented in [`transport_http.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/transport_http.go)). Both implement the internal `transport` interface with `call` and `notify` methods. The HTTP transport includes automatic re-initialization logic when sessions expire and spawns auxiliary clients for resource-intensive operations.

### How does the system handle MCP servers that expose read-only versus write-capable tools?

During tool discovery in `Client.listTools`, the system reads the MCP `readOnlyHint` annotation for each tool. The `remoteTool` struct stores this as a `ReadOnly` boolean, which the permission layer evaluates before execution. Additionally, administrators can override these hints by specifying `ReadOnlyToolNames` in the `Spec` configuration when adding a server, enforcing stricter security policies than the server itself declares.