mcplexer

Downstream Servers

A downstream server is an MCP-compliant tool provider that MCPlexer manages. Each downstream server exposes tools that clients can call through the gateway. MCPlexer handles process lifecycle, connection pooling, and tool namespacing.

Configuration Fields

NameTypeDefaultDescription
idstringUnique identifier (auto-generated or user-provided)
namestringHuman-readable display name
transport"stdio" | "http"How MCPlexer communicates with this server
commandstringProcess command to execute (stdio transport only)
argsstring[]Command-line arguments passed to the process
urlstringServer URL (http transport only)
tool_namespacestringPrefix for all tools from this server, e.g. "github"
discovery"static" | "dynamic""dynamic"How tools are discovered — cached on startup (static) or queried at runtime (dynamic)
capabilities_cacheobjectCached tools/list response (populated automatically for static discovery)
cache_configobjectTool response caching configuration
idle_timeout_secinteger300Seconds of inactivity before the process is stopped
max_instancesinteger1Maximum concurrent process instances per auth scope
restart_policy"always" | "on-failure" | "never""on-failure"How to handle process crashes
disabledbooleanfalseWhen true, this server's tools are hidden from clients
source"yaml" | "api" | "seed"How this server was configured

Transports

stdio

MCPlexer spawns the downstream server as a child process and communicates over stdin/stdout using JSON-RPC. This is the most common transport for local MCP servers.

yaml
downstream_servers:
  - name: "GitHub MCP"
    transport: stdio
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    tool_namespace: "github"

The process is started lazily on the first tool call and stopped after the idle timeout.

HTTP

MCPlexer connects to an already-running server over HTTP. Use this for remote or shared MCP servers.

yaml
downstream_servers:
  - name: "Internal Tools"
    transport: http
    url: "https://tools.internal.example.com/mcp"
    tool_namespace: "internal"

Tool Namespacing

Every tool from a downstream server is prefixed with the server's tool_namespace using a double underscore separator:

github__create_issue github__list_repos slack__send_message

Namespaces are always applied

Namespacing is not optional. Clients must always use the full namespace__toolname format. This prevents collisions when multiple servers expose tools with the same base name.

When a tool call arrives, MCPlexer splits on __ to determine which downstream server should handle it, then strips the prefix before forwarding.

Discovery Modes

Static Discovery

With discovery: "static", MCPlexer calls tools/list once when the server first starts and caches the result in capabilities_cache. Subsequent client requests for tool listings are served from the cache.

Use static discovery for servers whose tools never change at runtime.

Dynamic Discovery (Default)

With discovery: "dynamic" (the default for all non-internal servers), tools are not included in the aggregated tools/list response. Instead, clients discover tools through the built-in mcpx__search_tools and mcpx__load_tools tools. This keeps the initial tool listing small and avoids flooding the AI context window.

Codex compatibility

Codex clients don't support the load_tools pattern. Enable Codex dynamic tool compatibility in Settings to auto-include dynamic tools in tools/list for Codex sessions.

Downstream Notification Forwarding

When a downstream server sends a notifications/tools/list_changed notification (e.g., because its available tools changed), MCPlexer automatically:

  1. Invalidates the tools/list cache
  2. Forwards the notification to the connected AI client

This ensures clients always see up-to-date tool listings without manual cache flushing.

Tool Extras Passthrough

MCPlexer preserves all MCP tool metadata through the gateway pipeline, including annotations, title, and outputSchema. These fields are forwarded verbatim to the AI client alongside the standard name, description, and inputSchema fields.

Process Lifecycle

For stdio transport servers, MCPlexer manages the full process lifecycle:

  1. Lazy start — the process is not spawned until the first tool call targets it
  2. Active — the process handles tool calls over stdin/stdout
  3. Idle timeout — after idle_timeout_sec seconds without a tool call, the process is gracefully stopped
  4. Crash recovery — if the process exits unexpectedly, restart_policy determines the response

Restart Policies

PolicyBehavior
on-failureRestart automatically on non-zero exit (default)
alwaysRestart on any exit, including clean shutdown
neverDo not restart — tool calls will fail until manually restarted

Instance Pooling

Each downstream server instance is keyed by the tuple (server_id, auth_scope_id). This means if two clients use different auth scopes (e.g., different GitHub tokens), MCPlexer spawns separate process instances with the correct credentials injected.

The max_instances field caps how many concurrent instances can run for a single server. When the limit is reached, new tool calls queue until an instance becomes available.

yaml
downstream_servers:
  - name: "GitHub MCP"
    transport: stdio
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    tool_namespace: "github"
    max_instances: 3
    idle_timeout_sec: 600
    restart_policy: "on-failure"

Disabling a Server

Set disabled: true to hide a server's tools from all clients without removing the configuration. This is useful for maintenance or temporarily revoking access.

yaml
downstream_servers:
  - name: "GitHub MCP"
    disabled: true
    # ... rest of config

Disabled servers do not appear in tools/list responses, and tool calls to them return an error.

Example: Full Configuration

yaml
downstream_servers:
  - name: "GitHub MCP"
    transport: stdio
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    tool_namespace: "github"
    idle_timeout_sec: 300
    max_instances: 2
    restart_policy: on-failure

  - name: "Filesystem"
    transport: stdio
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
    tool_namespace: "fs"
    discovery: static
    idle_timeout_sec: 120

  - name: "Remote Analytics"
    transport: http
    url: "https://analytics.example.com/mcp"
    tool_namespace: "analytics"