dokploy-bundle-mcp
Provides a single MCP server for multiple Linear workspaces, routing tool calls to a specific workspace via a required routing argument.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@dokploy-bundle-mcpredeploy the api app on the production instance"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-multiplexer
Formerly dokploy-bundle-mcp.
One MCP server for all your Dokploy instances, or for all your accounts on any other MCP server, such as several Linear workspaces.
It runs one upstream connection per instance and adds a required argument to every tool (instance,
workspace or a name you pick) that says which instance the call goes to. Your MCP client (Claude Desktop,
Claude Code or any other) sees one set of tools, however many instances you have.
For Dokploy, the bundle wraps the official @dokploy/mcp.
Other MCP servers can be any stdio command or remote URL, with per-instance headers or a
per-instance OAuth login.
Why
If you register an MCP server once per instance, every instance adds its own copy of all its tools. Claude Code
lists every tool name in context at session start, even when it defers the schemas. @dokploy/mcp has 604
tools, so each Dokploy instance costs about 8k tokens before you've typed anything.
Setup | Tool names | ≈ Tokens |
6 separate | 3,624 | 50k |
This bundle, all tools | 606 | 6k |
This bundle, tags from | 159 | 1.6k |
With the bundle, that cost stays the same however many instances you add. Built-in connectors often reach only one account; Linear's, for example, connects to a single workspace. The bundle reaches all of them through one connection.
Related MCP server: MCP Gateway
Requirements
Node.js 20 or newer, and pnpm (npm works too)
For Dokploy: an API key for each instance. You generate these in Dokploy under your profile settings.
Install
git clone https://github.com/paulschwahn/mcp-multiplexer.gitcd mcp-multiplexer && pnpm install && pnpm buildConfigure
Each bundle has its own config file. The server reads the file you pass with --config, then
MCP_MULTIPLEXER_CONFIG, then ~/.config/mcp-multiplexer/config.json. The examples below are for Dokploy,
the default type; see Other MCP servers for the rest.
{
"tools": { "enabledTags": ["project", "environment", "application", "compose", "domain", "deployment"] },
"instances": {
"staging": {
"url": "https://dokploy-staging.example.com",
"apiKeyEnv": "DOKPLOY_STAGING_API_KEY",
"description": "Staging; hosts the PR preview deployments"
},
"production": {
"url": "https://dokploy.example.com",
"apiKey": "paste-key-here",
"description": "Production",
"protected": true
}
}
}If you put keys in the file, make it readable only by you (chmod 600). If you'd rather keep keys out of the
file, use apiKeyEnv and set the variable in your MCP client's server entry.
Already using @dokploy/mcp in Claude Desktop?
pnpm import-desktop-configThis copies every @dokploy/mcp entry from Claude Desktop's config into the bundle config: URL, key and any
extra DOKPLOY_* variables. Instance names come from the entry keys, so dokploy-acme-prod-mcp becomes
acme-prod. Names that contain prod are marked protected.
The script:
writes the file with mode 600;
refuses to overwrite an existing config unless you pass
--force;prints instance names only, never keys;
leaves the Desktop config alone and prints the entry to replace them with.
It finds the Desktop config on macOS, Windows and Linux. To read a different file, pass its path as an argument.
Config reference
Field | Meaning |
|
|
| Names this bundle's cache files. Defaults to the config file's name, e.g. |
| The routing argument added to every tool. Defaults to |
| One entry per instance. Names may only use |
| Shown by |
| Changes need your approval (see Protect production). |
| Extra environment for this instance's upstream process. |
| Dokploy base URL. A trailing |
| The key itself, or the name of an environment variable that holds it. Exactly one is required. |
| Environment for every instance's upstream, e.g. |
| Which tools to expose (see below). It applies to all instances. |
| Which |
Unknown fields are rejected, so a typo fails at startup rather than being silently ignored.
Choosing tools
The bundle passes upstream's own filter through. A tool's tag is the part of its name before the dash:
application-deploy has the tag application. Use either a preset or a list of enabledTags, and
optionally disabledTags to drop tags from it.
| Tags | Tools |
| project, application | 43 |
| minimal + server | 62 |
| core + environment, compose, domain, deployment | 119 |
| postgres, redis, mysql, mariadb, mongo, libsql | 94 |
| github, gitlab, bitbucket, gitea, gitProvider, registry, sshKey | 46 |
| everything | 604 |
Upgrading @dokploy/mcp
By default the bundle runs the @dokploy/mcp version pinned in package.json. To follow upstream releases
instead, set upstream.version:
{ "upstream": { "version": "latest" } }
| Runs |
| the version in |
| the newest release |
| the newest 0.30.x patch release |
| exactly that version |
How it works:
Checked once per start. When your client starts the server, the bundle asks npm which version the spec resolves to. A running session never switches versions.
Installed once per version. A new version is installed into
~/.cache/mcp-multiplexer/upstream/<version>with--ignore-scripts. The three newest versions are kept.Falls back when offline. If npm or the registry can't be reached, the bundle uses the newest cached version, or else the bundled one.
Easy to check.
bundle-instancesshows which version is running and why.
Before you switch, know two things:
Every API key goes to whatever version runs. With
latest, a new upstream release gets your keys without anyone reviewing it. For production instances, a patch range orbundledis the more careful choice.Upstream is still 0.x, so a minor release can rename or remove tools. This repo's tests only cover the bundled version.
Connect your client
The server talks MCP over stdio. The name you register it under becomes the tool prefix, so Dokploy registered
as dokploy gives mcp__dokploy__…. The approval hook below matches on that name.
Claude Desktop. Quit the app completely first, because it rewrites its config while running. Then add
this to claude_desktop_config.json and start the app again:
"mcpServers": {
"dokploy": {
"command": "/absolute/path/to/node",
"args": ["/path/to/mcp-multiplexer/dist/index.js"]
}
}For a second bundle, add another entry with "--config", "/path/to/its-config.json" after index.js in
args. Claude Desktop doesn't load your shell's PATH, so give node's absolute path (which node). This
matters especially if you use nvm.
Claude Code:
claude mcp add dokploy --scope user -- "$(which node)" /path/to/mcp-multiplexer/dist/index.jsAny other MCP client: run node /path/to/mcp-multiplexer/dist/index.js [--config <file>] as a stdio
server.
Use it
Start with a health check. Ask Claude to "run bundle-check". Each instance should come back with its Dokploy version, or with the reason it failed.
After that, just ask for what you want and name the instance:
"Show the last deployments of the api app on staging."
"Redeploy the web compose on production." On a protected instance, this asks for your approval first.
Every tool takes the required routing argument, and there is no default. Claude has to choose an instance on every call, and an unknown name is rejected with the list of valid ones. Two tools come from the bundle itself:
Tool | What it does |
| Lists name, description, whether it's protected, whether a connection is open, and what serves the instances. Never shows keys or header values. |
| Checks one instance, or all of them. For Dokploy it returns the version; for other types it calls |
Other MCP servers
Set type to bundle a server other than Dokploy. Every instance must serve the same tools: the bundle lists them
from the first instance that connects.
|
| Per instance |
|
|
|
|
|
|
|
|
|
checkTool names a read-only tool that bundle-check calls on each instance. Pick one that shows which account
the instance reached. Without it, bundle-check only pings.
OAuth logins go through mcp-remote, pinned in package.json, which
keeps each instance's tokens in its own directory. By default that's
~/.mcp-auth/mcp-multiplexer/<bundle>/<instance>. Point authDir at an existing MCP_REMOTE_CONFIG_DIR to
reuse a login you already have.
Example: Linear across several workspaces
~/.config/mcp-multiplexer/linear.json:
{
"type": "url",
"argument": "workspace",
"upstream": { "url": "https://mcp.linear.app/mcp", "auth": "oauth" },
"checkTool": "get_workspace",
"instances": {
"acme": { "description": "Acme (linear.app/acme)" },
"side-project": { "description": "Side project (linear.app/side)" }
}
}Register it as a second server:
claude mcp add linear --scope user -- "$(which node)" /path/to/mcp-multiplexer/dist/index.js --config ~/.config/mcp-multiplexer/linear.jsonThen ask Claude to "run bundle-check". It goes through the workspaces one at a time:
A workspace without a login opens Linear's sign-in in your browser.
Choose the workspace that instance is for on Linear's consent screen. Linear preselects the one you used last.
get_workspacethen shows which workspace each login reached. If one is wrong, delete that instance's token directory and run the check again.
Linear's MCP server may also accept a personal API key instead of OAuth. Third-party docs say so, but Linear's own docs don't confirm it:
"upstream": { "url": "https://mcp.linear.app/mcp" },
"instances": { "acme": { "headers": { "Authorization": "Bearer ${LINEAR_ACME_API_KEY}" } } }Protect production
A Claude Code PreToolUse hook asks for your approval before any tool that isn't read-only runs against an
instance marked "protected": true. Read-only tools still run without asking. Add one entry per bundle to
~/.claude/settings.json. Give each the same --config as its server, and a matcher with the name you
registered that server under:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__dokploy__.*",
"hooks": [
{ "type": "command", "command": "/absolute/path/to/node /path/to/mcp-multiplexer/dist/hook.js" }
]
},
{
"matcher": "mcp__linear__.*",
"hooks": [
{ "type": "command", "command": "/absolute/path/to/node /path/to/mcp-multiplexer/dist/hook.js --config ~/.config/mcp-multiplexer/linear.json" }
]
}
]
}
}What counts as read-only comes from upstream's readOnlyHint annotations: 226 of Dokploy's 604 tools, and 43 of
Linear's 82. Each time the server lists tools, it writes that list for the upstream it runs to
~/.cache/mcp-multiplexer/catalogs/<bundle>.json. Until a Dokploy server has started, the hook uses
dist/catalog.json from the build. If the hook can't read the config or doesn't know a tool, it asks.
Hooks only run in Claude Code: the CLI, the IDE extensions and the Code tab in Claude Desktop. In other clients,
protected is only a note in the tool description.
Troubleshooting
The tools don't appear in Claude Desktop. Check
~/Library/Logs/Claude/mcp-server-<name>.logon macOS. The usual cause is a node path that isn't absolute. If the config is invalid, the server exits at startup with a message naming the field.bundle-checkreportsHTTP 401 (API key rejected). The key is wrong or was revoked. A connection error means the URL is wrong; it should be the base URL you open in the browser.One instance fails and the others work. That's by design, for example when an
apiKeyEnvor${VAR}variable isn't set, or a login is missing. The error says which.An OAuth login doesn't open a browser. mcp-remote writes the sign-in URL to the server log; open it from there. The login waits for up to five minutes.
A config change has no effect. The server reads the config at startup, so restart your client.
upstream.versionisn't the version you expected. Ask forbundle-instances: itsupstreamfield shows the running version, and if npm failed, the reason it fell back.The hook doesn't prompt. Check that its matcher uses the server name from your client config and that it has the same
--configas the server. Then run it by hand, adjusting the tool and instance to a protected instance of yours:echo '{"tool_name":"mcp__dokploy__application-deploy","tool_input":{"instance":"production"}}' | node dist/hook.jsFor a protected instance this prints a
permissionDecision: "ask".
How it works
Listing tools: the bundle connects to the first instance that works and lists its tools. It puts the required routing argument first in each one, and adds
bundle-instancesandbundle-check. A tool that already has an argument with the routing argument's name is skipped, and the skip is logged.Calling a tool: the bundle removes the routing argument and forwards the call to that instance's own connection, which opens on first use and stays open. A stdio upstream gets only its own settings, plus PATH, HOME and similar, and never sees the other instances' keys.
Why
instanceand notserverfor Dokploy: Dokploy already uses "server" for its remote build hosts (serverId).
Development
pnpm test # stub and echo upstreams, an HTTP server, and the real @dokploy/mcp pointed at a dead URL
pnpm typecheckTo change the bundled @dokploy/mcp, bump it in package.json and run pnpm build, which regenerates
dist/catalog.json.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
The OpenRouter for tools. One MCP connection gives any AI agent 254 hosted tools, pay per call.
One MCP endpoint for Claude, GPT & Gemini: 100+ tools + no-code connectors + agent workers.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Connect MCP clients to 2,000+ AI models without managing provider API keys.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceRoutes MCP requests intelligently to multiple backend servers through a single gateway, bypassing tool limits by exposing only 2 routing tools while providing access to unlimited backend tools across services like GitHub, databases, and more.MIT
- AlicenseNot gradedqualityDmaintenanceReduces LLM context window overhead by proxying multiple MCP servers through a few efficient dispatch tools instead of registering hundreds of individual tool schemas. It supports multi-account routing and tool discovery for both CLI-based and persistent MCP server configurations.MIT
- AlicenseNot gradedqualityDmaintenanceActs as a proxy/router for multiple downstream MCP servers, exposing only meta-tools to the host to reduce token usage, enabling efficient search and invocation of tools from a fleet of servers.4 npmMIT
- AlicenseAqualityAmaintenanceEnables AI harnesses to connect to a single MCP endpoint that routes to multiple downstream MCP servers, discovering and executing capabilities on demand while keeping tool schemas out of context.413 npmApache 2.0