NAME

mcp-multibroker - combine MCP servers under tool name prefixes

SYNOPSIS

mcp-multibroker files 'stdio:mcp-fs /home/alice/src/project' \
                search https://example.org/mcp/ --bearer TOKEN
mcp-multibroker vim --- mcp-vim --bind :: --- --required

DESCRIPTION

mcp-multibroker combines several MCP servers behind one connection to your MCP client. You can give an assistant access to filesystem tools, Vim editors and other services by configuring them here, then connecting the client to this broker.

For each server, give a name prefix and an HTTP URL, a stdio:COMMAND, or --- PROGRAM ARG... --- to start it as a subprocess. These are the downstream servers. The multibroker starts or connects to them, obtains their tool definitions and forwards tool calls and replies between them and your client.

The prefix identifies which server a tool belongs to. For example, a filesystem server configured as files has a tool named files.read-lines. Your client can ask the multibroker which servers and tools are available, then call a tool by its prefixed name.

The connection from your MCP client to the multibroker is the upstream connection. It can use HTTP or stdin/stdout, independently of how the downstream servers connect. See AnyEvent::MCP::Intro for a complete setup example.

FIRST USE

Configure a local MCP client to start a multibroker with two servers:

mcp-multibroker --stdio \
   files --- mcp-fs --name project /home/alice/src/project --- \
   questions --- mcp-ask-user ---

The client launches only the broker; it starts children as needed. Ask:

Call discover-brokers and list-tools, then use files.workdirs
to show which project directories are configured.

files and questions are routing prefixes, not child instance names or permission profiles. Each child still applies its own permissions. The broker selects stdio automatically for bundled AnyEvent::MCP children. Third-party servers must support MCP over stdio in their own startup mode. You can replace a child with any stdio-speaking MCP server by putting its executable and arguments between the --- delimiters, or connect an existing HTTP server by URL; see "DOWNSTREAM SERVERS".

For HTTP clients, omit --stdio, leave the broker running, and obtain its upstream URL with --print-url using the same listener settings. For missing tools, try discover-brokers for connection errors and list-tools to refresh discovery; your MCP client may also need to refresh its cached list. --tool-help only shows the broker's own tools, not those of its children. Ordinary children start lazily. Use --eager after the child specification for services that accept independent connections, such as Vim or browser brokers.

TOOLS

The broker exposes three tools of its own:

discover-brokers

Lists the configured downstream servers and their instructions by prefix. An entry is a string of instructions, null for a reachable server with no instructions, or {error: MESSAGE} for an unreachable server.

list-tools

Queries downstream servers for their tool definitions and refreshes the combined list. An optional broker argument selects one exact downstream prefix, excluding the multibroker's own tools. Unknown broker names or arguments are errors. Unreachable servers are omitted from tool listings but reported by discover-brokers.

call-tool

Calls a downstream tool by its PREFIX.TOOL name with the supplied arguments. Calls to an unavailable server fail as tool errors; calls are not replayed after transport failures.

A refreshed list is a snapshot of what is reachable at that point. It includes configured servers and currently registered reverse connections. A listed server might become unavailable before a later call.

DOWNSTREAM SERVERS

Each prefix identifies one downstream server. HTTP URLs may include a URL-path token, or be followed by --bearer TOKEN. bearer and timeout defaults are read from [client PREFIX] in the global configuration; the URL in the command line overrides any configured url. Stdio children receive PERL_ANYEVENT_MCP_MULTIBROKER_PROTOCOL=stdio and PERL_ANYEVENT_MCP_MULTIBROKER_ALIAS=PREFIX in their environment. The alias may be displayed in downstream permission prompts, without changing the server's instance name or permission rules.

For direct execution, use PREFIX --- PROGRAM ARG... ---:

mcp-multibroker vim --- mcp-vim --bind :: --- --eager \
                files --- mcp-fs '/home/alice/project with spaces' ---

The next exact --- argument closes the list. Everything inside, including --, --eager and --bearer, is passed unchanged to the child. There is no shell interpretation. Empty arguments are preserved; the program must be nonempty. An empty list or missing closing delimiter is an error.

A subprocess can also connect to an MCP server on another machine via SSH:

mcp-multibroker remote-fs --- ssh remotehost mcp-fs --stdio --ask mcp /workdir ---

--stdio tells the remote mcp-fs to use stdin/stdout for MCP. It must be specified explicitly because the multibroker's environment variables are not passed through SSH. --ask mcp forwards permission prompts over MCP to the local multibroker, instead of trying to display dialogs on the remote host. The local broker then presents them according to its own --ask mode (normally using a local helper); see "PERMISSION PROMPTS".

stdio:COMMAND retains shell interpretation. Use it for shell syntax or when a child needs a literal --- argument. Quoting '---' in the delimited form cannot escape the delimiter: shell quoting is already gone.

Broker-wide options precede the first server entry. Downstream options follow that server's URL or closing ---; Getopt::Long stops at the next prefix. Options inside an argv list belong exclusively to the child.

--eager after either stdio form starts that child at multibroker startup rather than waiting for its first MCP use. This is useful for children that also provide their own TCP service, such as mcp-vim. Other stdio children remain lazy. Eager startup attempts one connection and logs failures; it does not continually restart failed children. --required implies --eager and makes the multibroker exit with failure if that child cannot initialize or later disconnects unexpectedly. A supervisor can then restart the broker. An explicitly accepted managed idle shutdown is not a failure; the child can be started again on the next use. Both options apply only to spawned stdio children, never remote HTTP peers.

Reverse connections

The HTTP listener also accepts authenticated reverse MCP connections. It can run with no configured downstream servers. With upstream stdio, add --listen to keep this listener active:

mcp-multibroker --stdio --listen --bind 127.0.0.1:31740
mcp-multibroker --bind 127.0.0.1:31740 --print-url
mcp-fs --connect http://127.0.0.1:31740/TOKEN/files /home/alice/project

Use the token returned by --print-url. The connecting server extracts the host, port, token and alias from this URL, sends ["mcp-connect", token, alias] followed by a newline, then ordinary MCP JSONL. The connection is raw TCP on the HTTP listener, not an HTTP request. It uses the same accepted tokens as HTTP. Authentication grants registration under any free alias; configured or connected aliases cannot be replaced.

The connecting program remains the MCP server. The multibroker attaches an MCP client and discovers its tools immediately. Forms and cancellation follow the same routes as other downstream connections. A disconnect fails pending calls and removes the registration; the server retries once per second, up to sixty attempts, and registers again when the connection is restored. No interrupted operation is replayed. Plain HTTP endpoint URLs do not provide transport encryption; use an existing trusted connection or tunnel as needed.

Process lifetime

Each downstream connection advertises anyevent-mcp/multibroker version 1 in its client capabilities. A spawned stdio child can request managed idle shutdown with anyevent-mcp/idle-exit. When no calls are outstanding, the broker may send anyevent-mcp/shutdown; the child acknowledges before exiting. This is optional and never makes HTTP children exit. Unexpected disconnects are logged, not proactively restarted. A later use reconnects.

PERMISSION PROMPTS

--ask helper (the default) answers downstream form challenges locally. Ordinary forms use anyevent-mcp-form-elicitation; permission requests keep their specialised permission dialogs. --ask mcp forwards modern challenges to the upstream client. --ask auto uses upstream form support when advertised, otherwise the local helper. A decline or cancel is never retried through another interface. If a client advertises forms but silently declines them, select helper explicitly.

--ask tty and --ask yad select only the built-in terminal or YAD interface for both ordinary forms and permission dialogs. These modes ignore custom helper variables and do not fall back to another interface.

The broker advertises its local form support to downstream calls when it will handle their forms itself. In mcp mode it forwards the upstream caller's capabilities instead. Local forms work for HTTP and stdio children, including when the upstream caller cannot handle native input challenges. Continuation tokens remain opaque; the broker sends the human response back to the same downstream operation and returns its eventual result.

The mode also sets PERL_ANYEVENT_MCP_PERMISSION_ASK for spawned stdio children. Independently running servers keep their own configuration. Spawned mcp-ask-user instances normally delegate presentation to this broker. Use --ask mcp on a separately started mcp-ask-user to delegate to its connecting client, including a multibroker.

Local permission and question dialogs share one queue. Set PERL_ANYEVENT_MCP_ELICITATION_HELPER to replace the form helper and PERL_ANYEVENT_MCP_PERMISSION_HELPER to replace the permission helper. Cancelling a tool call cancels its downstream operation and pending local prompt. The original stdio permission extension is also supported.

CLIENT CONNECTION

The upstream listener uses "mcp_start" in AnyEvent::MCP::Server::Simple: HTTP without stdout output by default, or stdio when run by another broker. The service instance name defaults to default. Repeated --token TOKEN options before the broker pairs configure credentials accepted by the upstream HTTP listener. They are separate from --bearer after a downstream broker URL, which authenticates the client connection. --name NAME selects [instance NAME] in the global configuration, including its credentials and optional bind address. --bind ADDRESS[:PORT] overrides that address. The default is IPv4 loopback with a stable name-derived port in 16384..32767. Port zero requests an ephemeral port. --print-url prints the credential-bearing URL without opening a listener or starting children; it requires a nonzero port. Upstream stdio startup skips HTTP listener configuration unless --listen is specified; named HTTP children still load their client defaults. Browser origins are unrestricted by default; HTTP requests still require an accepted token. See AnyEvent::MCP::Intro for examples.

OPTIONS

Put listener options before the first prefix/URL pair. Options applying to one downstream server follow that pair immediately. Common options, including --name, --bind, --token, --stdio, --connect, --print-url, --auto-allow and --auto-deny, are documented in "mcp_getoptions" in AnyEvent::MCP::Server::Simple. They configure the upstream server; --token is distinct from the downstream --bearer. The default upstream instance name is default and does not affect downstream prefixes. --tool-help lists only the multibroker's own tools; it does not connect to downstream servers to query their registries.

--listen

Keep the HTTP and extension listener active alongside upstream stdio or --connect. Without stdio or --connect, HTTP is already the default.

--ask $method

Select local or upstream form prompting for all downstream connections. Also pass the mode as the permission default to spawned stdio children. See "PERMISSION PROMPTS".

--bearer $token

Authenticate the preceding HTTP child connection with this bearer token. This option does not configure the upstream listener.

--eager

Start the preceding stdio child immediately. HTTP children cannot use this option.

--required

Require the preceding stdio child to initialize and stay connected. Implies --eager; unexpected disconnect exits the multibroker with failure. Managed idle shutdown is allowed.