Skip to main content
Glama

mcp-mux: multiple accounts for any MCP server

CI License: MIT Runtime: Bun MCP

mcp-mux lets Claude Code and Codex use multiple accounts of the same MCP server at once: two Gmail inboxes, a work and a client Slack, several Notion or Linear workspaces. It runs as one local MCP server. Every tool gets a required account argument, each account signs in with its own OAuth session, and tokens are kept in the macOS Keychain.

mcp-mux: one MCP server, many accounts

Today, Claude connectors take one account per connector (anthropics/claude-code#27302, 422 👍, no official answer). Slack takes one workspace (#44243). Codex added multiple accounts only for Gmail, Calendar and Contacts (openai/codex#20500). mcp-mux works with any MCP server you can run or reach locally.

Quick start

bun add -g github:majiayu000/mcp-mux        # needs Bun ≥ 1.3

Write notion.mux.json. Accounts share the upstream and override only what differs:

{
  "name": "notion",
  "upstream": { "url": "https://mcp.notion.com/mcp", "oauth": {} },
  "accounts": {
    "work":     { "description": "Acme workspace", "oauth": { "authorizeParams": { "login_hint": "me@acme.com" } } },
    "personal": { "description": "personal notes" }
  }
}

Sign in each account once, check them, and add the server to your client:

mcp-mux login notion.mux.json work
mcp-mux login notion.mux.json personal
mcp-mux status notion.mux.json

claude mcp add notion -- mcp-mux serve /abs/path/notion.mux.json     # Claude Code
codex mcp add notion -- mcp-mux serve /abs/path/notion.mux.json      # Codex

The model now sees each Notion tool once, with account: "work" | "personal" required.

Related MCP server: Multi-Gmail Connector

What it looks like

This run uses the bundled mock OAuth server (examples/mock-oauth-server.ts): two fake users behind one URL, told apart only by their tokens, the same way Gmail or Notion work.

$ mcp-mux login mail.mux.json work
[mcp-mux] logged in "work" (2 tools available)
$ mcp-mux login mail.mux.json home
[mcp-mux] logged in "home" (2 tools available)
$ mcp-mux status mail.mux.json
work    ok (2 tools)    http: http://127.0.0.1:47821/mcp
home    ok (2 tools)    http: http://127.0.0.1:47821/mcp

# what the client sees after `mcp-mux serve mail.mux.json`
tools: whoami, search_mail
account: {"type":"string","enum":["work","home"],"description":"Which account to use: work (alice@work.example), home (alice@home.example)"}
whoami({"account":"work"})                    -> alice@work.example
whoami({"account":"home"})                    -> alice@home.example
search_mail({"account":"work","query":"e"})   -> ["Q3 budget review moved to Friday","Invoice #4411 from Acme"]
search_mail({"account":"home","query":"e"})   -> ["Dentist appointment reminder","Your library books are due"]
whoami({"account":"nope"})                    -> error -32602: unknown account "nope"; expected one of: work, home

The same flow ran in Claude Code: claude mcp list reported ✔ Connected for both a filesystem mux and this OAuth mux.

Features

  • One toolset, many accounts. The client lists each upstream tool once, with a required account enum. Optional description labels (an email address, a workspace name) are shown to the model next to each account.

  • Fails closed. A missing or unknown account is an error. A logged-out account returns an error naming the mcp-mux login command to run. mcp-mux never falls back to another account or returns an empty result in its place.

  • Per-account OAuth 2.1 for remote servers: protected resource discovery, dynamic client registration or a pre-registered client, PKCE, RFC 8707 resource, RFC 9207 iss check, and automatic refresh. authorizeParams pins a login to one identity, for example Google login_hint or Slack team.

  • Isolated credentials. Each account gets its own Keychain entry (mcp-mux / <name>/<account>), or an AES-256-GCM encrypted file on other platforms. A stdio account sees only its own environment variables, never another account's secrets.

  • Self-healing. If an upstream crashes, or you log an account in while the client is running, the next call reconnects. No client restart needed.

  • stdio and Streamable HTTP upstreams, with ${ENV} expansion that fails loudly when a variable is unset.

Compared with

mcp-mux

mcp-multiauth

mcp-remote ×N

Claude Code natively

Codex natively

One toolset + account argument

✅

✅

✗ tools duplicated per account

✗ one server per account

✗

Per-account OAuth login

✅

✗ static headers / env only

✅

◐ one login per endpoint

◐ Gmail / Calendar / Contacts only

Pin the identity at login (login_hint, team)

✅

✗

✅

✗

✗

Encrypted or keychain token storage

✅

✗

✗ not documented

✅

cloud

stdio upstreams

✅

✅

✗

✅

✅

Live check of every account

✅ status

✗

◐ one server

◐ claude mcp list

✗

Full comparison, including the self-hosted gateways: docs/comparison.md.

Configuration

One file describes one upstream server and its accounts. upstream is shared by all accounts, and each account overrides only what differs. env, headers, oauth and oauth.authorizeParams are merged one level deep.

Local (stdio) server, where each account gets different arguments or environment variables:

{
  "name": "fs",
  "upstream": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem"] },
  "accounts": {
    "work":     { "args": ["-y", "@modelcontextprotocol/server-filesystem", "${MUX_WORK_DIR}"] },
    "personal": { "args": ["-y", "@modelcontextprotocol/server-filesystem", "${MUX_PERSONAL_DIR}"],
                  "env": { "API_TOKEN": "${PERSONAL_TOKEN}" } }
  }
}

Field

Meaning

name

Server name, also the credential key prefix. [A-Za-z0-9_-].

upstream.command / args / env / cwd

stdio upstream.

upstream.url / headers

Streamable HTTP upstream.

upstream.oauth

Present means the upstream uses OAuth. Optional clientId, clientSecret, scope, callbackPort, authorizeParams.

accounts.<name>

Same fields as upstream, plus description (shown to the model).

store

{"type":"keychain","keychain":"<path>"} or {"type":"file","path":"<path>"}. The default is the login keychain on macOS, and ~/.config/mcp-mux/credentials.json elsewhere.

Some servers don't support dynamic client registration; Slack is one. For those, register an OAuth app yourself and set oauth.clientId, oauth.clientSecret, and a fixed oauth.callbackPort whose redirect URI http://127.0.0.1:<port>/callback is registered with the app.

Secrets: ${VAR} is read from mcp-mux's own environment. MCP clients start servers with only the variables you configure, so put secrets (and MCP_MUX_STORE_KEY when using the file store) in the client's env for this server, for example claude mcp add notion -e MCP_MUX_STORE_KEY=... -- mcp-mux serve ....

Client config files

Claude Code .mcp.json:

{ "mcpServers": { "notion": { "command": "mcp-mux", "args": ["serve", "/abs/path/notion.mux.json"] } } }

Codex ~/.codex/config.toml:

[mcp_servers.notion]
command = "mcp-mux"
args = ["serve", "/abs/path/notion.mux.json"]

Commands

mcp-mux serve  <config.json>                          run as a stdio MCP server
mcp-mux login  <config.json> <account> [--no-browser] OAuth login for one account
mcp-mux logout <config.json> <account>                delete one account's stored credentials
mcp-mux status <config.json>                          connect to every account and report; exit 1 if any fails

Try it without real accounts

git clone https://github.com/majiayu000/mcp-mux && cd mcp-mux && bun install
bun examples/mock-oauth-server.ts                          # http://127.0.0.1:47821/mcp
bun src/cli.ts login examples/mock-mail.mux.json work      # login_hint pins alice@work.example
bun src/cli.ts login examples/mock-mail.mux.json home      # pick alice@home.example on the consent page
claude mcp add mail -- bun "$PWD/src/cli.ts" serve "$PWD/examples/mock-mail.mux.json"

Limitations

  • OAuth has only been tested against the bundled mock provider, not real Gmail, Slack or Notion. Reports from real providers are welcome.

  • Local clients only: Claude Code, Codex, Claude Desktop. claude.ai on the web, the mobile apps and cloud routines connect only to remote URLs.

  • Only tools are proxied. Resources, prompts and tools/list_changed aren't passed through yet.

  • All accounts must expose the same tools. mcp-mux refuses to start otherwise and lists the difference.

  • The MCP TypeScript SDK supports protocol versions up to 2025-11-25. Upstreams that only speak 2026-07-28 won't connect yet.

Development

bun install
bun test           # config, stores (incl. a throwaway macOS keychain), real filesystem server, OAuth end to end
bun run typecheck

Design and research notes: docs/design.md, docs/research.md, docs/comparison.md.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI clients to manage multiple accounts per provider (e.g., Notion workspaces, GitHub identities) through a single MCP endpoint, with sticky account selection and context switching.
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables Claude to search and read multiple Gmail or Google Workspace mail accounts at once through a local, read-only MCP server, with every result labeled by the account it came from. Tokens stay in the OS keychain, no server is run by the authors, and write actions such as sending, deleting, or archiving are not supported.
    8 npm
    1
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables Claude Code and Claude Desktop to search, read, and manage multiple named Gmail inboxes from a self-hosted MCP server, keeping each account's results separate and authenticated through user-owned OAuth tokens.
    11 npm
    MIT