mcpmux

package
v0.0.0-...-451ec7f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 9, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

mcpmux

A meta-MCP proxy in Go that lets a coding agent dynamically register, reload, and terminate child MCP servers at runtime through MCP tools themselves. Today, mcpmux multiplexes management tools plus child tools behind one stdio MCP server. It does not yet multiplex the full MCP feature surface.

┌──────────────┐         ┌──────────────────────────────┐
│  MCP Client  │◄─stdio─►│          mcpmux               │
│              │         │  ┌────────────────────────┐   │
│              │         │  │ Management Tools:       │   │
│              │         │  │  • add_server           │   │
│              │         │  │  • remove_server        │   │
│              │         │  │  • reload_server        │   │
│              │         │  │  • list_servers         │   │
│              │         │  └────────────────────────┘   │
│              │         │  ┌────────────────────────┐   │
│              │         │  │ Proxied Tools:          │   │
│              │         │  │  • myserver__tool_a     │   │
│              │         │  │  • myserver__tool_b     │   │
│              │         │  │  • other__tool_x        │   │
│              │         │  └────────────────────────┘   │
│              │         │         │          │          │
│              │         │    ┌────▼───┐ ┌───▼────┐     │
│              │         │    │child 1 │ │child 2 │ ... │
│              │         │    │(stdio) │ │(stdio) │     │
│              │         │    └────────┘ └────────┘     │
└──────────────┘         └──────────────────────────────┘

Usage

bazel build //mcpmux/cmd/mcpmux
bazel-bin/mcpmux/cmd/mcpmux/mcpmux_/mcpmux

The proxy starts with no child servers. Use the management tools to add them:

// tools/call add_server
{"name": "mytools", "command": "node", "args": ["./my-mcp-server.js"]}

// tools/call mytools__some_tool
{"arg1": "value"}

// tools/call reload_server
{"name": "mytools"}

// tools/call remove_server
{"name": "mytools"}

// tools/call list_servers
{}

What's implemented

The current server initializes as MCP 2025-11-25 and advertises:

  • tools with listChanged
  • logging
Management tools
Tool Description
add_server Spawn a child process, MCP handshake, discover tools, expose them with {name}__{tool} prefix
remove_server Remove tools from proxy, gracefully shut down child (stdin close -> SIGTERM -> SIGKILL)
reload_server Remove + re-add with the same config (re-discovers tools)
list_servers List all servers with name, command, args, status, tools, PID, uptime
Tool proxying
  • Child tools are namespaced as {server}__{tool} (split on first __)
  • The child tool's description and inputSchema are preserved
  • Other tool metadata is not currently preserved during re-registration
  • Tool calls are forwarded to the correct child's MCP session
  • notifications/tools/list_changed is sent to the client on add/remove/reload
Child process lifecycle
  • Spawned via os/exec with configurable command, args, env, cwd
  • MCP initialize handshake via the SDK's CommandTransport
  • Child stderr forwarded to proxy stderr with [name] prefix
  • Crash detection via session.Wait() — marks server as crashed, removes its tools
  • Graceful shutdown with timeout, falls back to kill if in-flight calls block
  • Single-use Child instances with enforced state machine: New -> Starting -> Running -> Stopped|Crashed
Transport
  • Proxy serves over stdio (reads stdin, writes stdout)
  • Child servers connected over stdio (per-child stdin/stdout pipes)

What's NOT implemented

These MCP spec features are not proxied through mcpmux today:

Feature What it means Spec methods
Resources Child file/data resources are invisible to client resources/list, resources/read, resources/templates/list, resources/subscribe, resources/unsubscribe
Prompts Child prompt templates are invisible to client prompts/list, prompts/get
Completions Tab-completion for prompts/resources not forwarded completion/complete
Logging Child log messages not relayed to client logging/setLevel, notifications/message
Sampling Children cannot request LLM completions through proxy sampling/createMessage (server->client)
Roots Children cannot discover workspace roots roots/list (server->client)
Elicitation Children cannot request structured user input through the proxy elicitation/create, notifications/elicitation/complete
Tasks Task-augmented requests and task inspection/cancellation are not forwarded tasks/list, tasks/get, tasks/result, tasks/cancel
Cancellation / progress Cancellation and progress notifications are not explicitly bridged between child and client notifications/cancelled, notifications/progress
Tool metadata Re-registered tools drop metadata beyond description and inputSchema title, icons, annotations, outputSchema, extension fields
Dynamic tool discovery If a child adds/removes tools at runtime, proxy doesn't notice until reload_server notifications/tools/list_changed (child->proxy direction)

Additional current limitations:

  • Child MCP notifications are not generally bridged to the client
  • Child server capabilities are not merged into the mux capability advertisement
  • Only child tools are discovered at startup; prompts and resources are not listed or merged
Transport limitations
  • No SSE/HTTP child transports (stdio only)
  • No config file for pre-registering servers on startup

Testing

# All tests
bazel test //mcpmux/...

# Unit tests only
bazel test //mcpmux:mcpmux_test

# Integration tests (spawns real processes)
bazel test //mcpmux/integration:integration_test

# With race detector
bazel test //mcpmux/... --@rules_go//go/config:race

Security

mcpmux spawns arbitrary child processes via add_server. If the client is not already sandboxed, run mcpmux inside //sandbox to constrain filesystem and process-level effects of child MCP servers.

Documentation

Overview

Package mcpmux implements a meta-MCP proxy that manages multiple child MCP servers behind a single unified MCP interface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func PrefixTool

func PrefixTool(server, tool string) string

PrefixTool returns the namespaced tool name "{server}__{tool}".

func SplitTool

func SplitTool(name string) (server, tool string, ok bool)

SplitTool splits a namespaced tool name on the first "__" separator. Returns the server name, the original tool name, and true if the name contained the separator. Returns ("", name, false) otherwise.

func ValidateServerName

func ValidateServerName(name string) error

ValidateServerName checks that a server name is valid for use as a namespace prefix. It must be non-empty and must not contain the namespace separator.

Types

type Child

type Child struct {
	Config    ChildConfig
	StartTime time.Time
	// contains filtered or unexported fields
}

Child manages a single child MCP server process.

func NewChild

func NewChild(cfg ChildConfig, logger *slog.Logger, onCrash func()) *Child

NewChild creates a new Child from the given config.

func (*Child) PID

func (c *Child) PID() int

PID returns the child's process ID.

func (*Child) Session

func (c *Child) Session() *mcp.ClientSession

Session returns the MCP client session for making tool calls.

func (*Child) Start

func (c *Child) Start(ctx context.Context) ([]*mcp.Tool, error)

Start spawns the child process, performs the MCP handshake, and discovers tools. It starts a background goroutine to monitor the child for crashes. Each Child instance may only be started once (StatusNew → StatusStarting).

func (*Child) Status

func (c *Child) Status() ChildStatus

Status returns the current status.

func (*Child) Stop

func (c *Child) Stop() error

Stop shuts down the child process. It first attempts a graceful close (which does stdin close → SIGTERM → SIGKILL via the SDK). If the graceful close blocks (e.g. due to in-flight tool calls), it kills the process after a timeout to unblock it.

func (*Child) Tools

func (c *Child) Tools() []*mcp.Tool

Tools returns the discovered tools.

type ChildConfig

type ChildConfig struct {
	Name    string            `json:"name"`
	Command string            `json:"command"`
	Args    []string          `json:"args,omitempty"`
	Env     map[string]string `json:"env,omitempty"`
	Cwd     string            `json:"cwd,omitempty"`
}

ChildConfig holds the spawn configuration for a child MCP server.

type ChildStatus

type ChildStatus int

ChildStatus represents the lifecycle state of a child server.

const (
	StatusNew      ChildStatus = iota // created, not yet started
	StatusStarting                    // Start in progress
	StatusRunning                     // healthy and connected
	StatusStopped                     // intentionally shut down
	StatusCrashed                     // exited unexpectedly
)

func (ChildStatus) String

func (s ChildStatus) String() string

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy is the core MCP proxy that manages child servers and exposes their tools through a single unified MCP server.

func NewProxy

func NewProxy(logger *slog.Logger) *Proxy

NewProxy creates a new Proxy with management tools registered.

func (*Proxy) Server

func (p *Proxy) Server() *mcp.Server

Server returns the underlying MCP server (for running with a transport).

func (*Proxy) Shutdown

func (p *Proxy) Shutdown()

Shutdown stops all child servers.

Directories

Path Synopsis
cmd
mcpmux command
mcpmux is a meta-MCP proxy that manages multiple child MCP servers.
mcpmux is a meta-MCP proxy that manages multiple child MCP servers.
test
crashserver command
crashserver is an MCP server that exposes a "crash" tool which calls os.Exit(1).
crashserver is an MCP server that exposes a "crash" tool which calls os.Exit(1).
echoserver command
echoserver is a minimal MCP server for testing.
echoserver is a minimal MCP server for testing.
echoserver_v2 command
echoserver_v2 extends echoserver with an additional "reverse" tool.
echoserver_v2 extends echoserver with an additional "reverse" tool.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL